BATS fell down pushing a process into the background, so I switched to shunit2 /...
[certmaster.git] / certmaster / utils.py
old mode 100755 (executable)
new mode 100644 (file)
index 54c9c39..e348ec4
@@ -1,5 +1,5 @@
 """
 """
-Copyright 2007, Red Hat, Inc
+Copyright 2007-2008, Red Hat, Inc
 see AUTHORS
 
 This software may be freely redistributed under the terms of the GNU
 see AUTHORS
 
 This software may be freely redistributed under the terms of the GNU
@@ -16,9 +16,27 @@ import sys
 import traceback
 import xmlrpclib
 import socket
 import traceback
 import xmlrpclib
 import socket
+import time
+import glob
 
 
+import codes
+import certs
+from config import read_config
+from commonconfig import MinionConfig
+import logger
+import sub_process
+
+# FIXME: module needs better pydoc
+
+# FIXME: can remove this constant?
 REMOTE_ERROR = "REMOTE_ERROR"
 
 REMOTE_ERROR = "REMOTE_ERROR"
 
+# The standard I/O file descriptors are redirected to /dev/null by default.
+if (hasattr(os, "devnull")):
+    REDIRECT_TO = os.devnull
+else:
+    REDIRECT_TO = "/dev/null"
+
 def trace_me():
     x = traceback.extract_stack()
     bar = string.join(traceback.format_list(x))
 def trace_me():
     x = traceback.extract_stack()
     bar = string.join(traceback.format_list(x))
@@ -30,37 +48,45 @@ def daemonize(pidfile=None):
     Writes the new PID to the provided file name if not None.
     """
 
     Writes the new PID to the provided file name if not None.
     """
 
-    print pidfile
     pid = os.fork()
     if pid > 0:
         sys.exit(0)
     pid = os.fork()
     if pid > 0:
         sys.exit(0)
+    os.chdir("/")
     os.setsid()
     os.setsid()
-    os.umask(0)
+    os.umask(077)
     pid = os.fork()
 
     pid = os.fork()
 
+    os.close(0)
+    os.close(1)
+    os.close(2)
+
+    # based on http://code.activestate.com/recipes/278731/
+    os.open(REDIRECT_TO, os.O_RDWR)     # standard input (0)
+
+    os.dup2(0, 1)                       # standard output (1)
+    os.dup2(0, 2)                       # standard error (2)
+
+
+
     if pid > 0:
         if pidfile is not None:
             open(pidfile, "w").write(str(pid))
         sys.exit(0)
 
     if pid > 0:
         if pidfile is not None:
             open(pidfile, "w").write(str(pid))
         sys.exit(0)
 
+
 def nice_exception(etype, evalue, etb):
     etype = str(etype)
 def nice_exception(etype, evalue, etb):
     etype = str(etype)
-    lefti = etype.index("'") + 1
-    righti = etype.rindex("'")
-    nicetype = etype[lefti:righti]
+    try:
+        lefti = etype.index("'") + 1
+        righti = etype.rindex("'")
+        nicetype = etype[lefti:righti]
+    except:
+        nicetype = etype
     nicestack = string.join(traceback.format_list(traceback.extract_tb(etb)))
     nicestack = string.join(traceback.format_list(traceback.extract_tb(etb)))
-    return [ REMOTE_ERROR, nicetype, str(evalue), nicestack ] 
-
-def get_hostname():
-    fqdn = socket.getfqdn()
-    host = socket.gethostname()
-    if fqdn.find(host) != -1:
-        return fqdn
-    else:
-        return host
-
+    return [ REMOTE_ERROR, nicetype, str(evalue), nicestack ]
 
 def is_error(result):
 
 def is_error(result):
+    # FIXME: I believe we can remove this function
     if type(result) != list:
         return False
     if len(result) == 0:
     if type(result) != list:
         return False
     if len(result) == 0:
@@ -69,5 +95,154 @@ def is_error(result):
         return True
     return False
 
         return True
     return False
 
+def get_hostname(talk_to_certmaster=True):
+    """
+    "localhost" is a lame hostname to use for a key, so try to get
+    a more meaningful hostname. We do this by connecting to the certmaster
+    and seeing what interface/ip it uses to make that connection, and looking
+    up the hostname for that.
+    """
+    # FIXME: this code ignores http proxies (which granted, we don't
+    #      support elsewhere either.
+    hostname = None
+    hostname = socket.gethostname()
+    # print "DEBUG: HOSTNAME TRY1: %s" % hostname
+    try:
+        ip = socket.gethostbyname(hostname)
+    except:
+        return hostname
+    if ip != "127.0.0.1":
+        return hostname
+
+
+# FIXME: move to requestor module and also create a verbose mode
+# prints to the screen for usage by /usr/bin/certmaster-request
+
+def create_minion_keys(hostname=None, ca_name=''):
+    log = logger.Logger().logger
+
+    # FIXME: paths should not be hard coded here, move to settings universally
+    config_file = '/etc/certmaster/minion.conf'
+    config = read_config(config_file, MinionConfig)
+
+    try:
+        certauth=config.ca[ca_name]
+    except:
+        raise codes.CMException("Unknown cert authority: %s" % ca_name)
+
+    cert_dir = certauth.cert_dir
+        
+    master_uri = 'http://%s:%s/' % (config.certmaster, config.certmaster_port)
+
+    hn = hostname
+    if hn is None:
+        hn = get_hostname()
+
+    if hn is None:
+        raise codes.CMException("Could not determine a hostname other than localhost")
+    else:
+        # use lowercase letters for hostnames
+        hn = hn.lower()
+
+    key_file = '%s/%s.pem' % (cert_dir, hn)
+    csr_file = '%s/%s.csr' % (cert_dir, hn)
+    cert_file = '%s/%s.cert' % (cert_dir, hn)
+    ca_cert_file = '%s/ca.cert' % cert_dir
+
+    if os.path.exists(cert_file) and os.path.exists(ca_cert_file):
+        # print "DEBUG: err, no cert_file"
+        return
+
+    keypair = None
+    try:
+        if not os.path.exists(cert_dir):
+            os.makedirs(cert_dir)
+        if not os.path.exists(key_file):
+            keypair = certs.make_keypair(dest=key_file)
+        if not os.path.exists(csr_file):
+            if not keypair:
+                keypair = certs.retrieve_key_from_file(key_file)
+            csr = certs.make_csr(keypair, dest=csr_file, hostname=hn)
+    except Exception, e:
+        traceback.print_exc()
+        raise codes.CMException, "Could not create local keypair or csr for session"
+
+    result = False
+
+    while not result:
+        try:
+            # print "DEBUG: submitting CSR to certmaster: %s" % master_uri
+            log.debug("submitting CSR: %s  to certmaster %s" % (csr_file, master_uri))
+            result, cert_string, ca_cert_string = submit_csr_to_master(csr_file, master_uri, ca_name)
+        except socket.error, e:
+            log.warning("Could not locate certmaster at %s" % master_uri)
+
+        # logging here would be nice
+        if not result:
+            # print "DEBUG: no response from certmaster, sleeping 10 seconds"
+            log.warning("no response from certmaster %s, sleeping 10 seconds" % master_uri)
+            time.sleep(10)
+
+
+    if result:
+        # print "DEBUG: recieved certificate from certmaster"
+        log.debug("received certificate from certmaster %s, storing to %s" % (master_uri, cert_file))
+        if not keypair:
+            keypair = certs.retrieve_key_from_file(key_file)
+        valid = certs.check_cert_key_match(cert_string, keypair)
+        if not valid:
+            log.info("certificate does not match key (run certmaster-ca --clean first?)")
+            sys.stderr.write("certificate does not match key (run certmaster-ca --clean first?)\n")
+            return
+        cert_fd = os.open(cert_file, os.O_RDWR|os.O_CREAT, 0644)
+        os.write(cert_fd, cert_string)
+        os.close(cert_fd)
+
+        ca_cert_fd = os.open(ca_cert_file, os.O_RDWR|os.O_CREAT, 0644)
+        os.write(ca_cert_fd, ca_cert_string)
+        os.close(ca_cert_fd)
+
+def run_triggers(ref, globber):
+    """
+    Runs all the trigger scripts in a given directory.
+    ref can be a certmaster object, if not None, the name will be passed
+    to the script.  If ref is None, the script will be called with
+    no argumenets.  Globber is a wildcard expression indicating which
+    triggers to run.  Example:  "/var/lib/certmaster/triggers/blah/*"
+    """
+
+    log = logger.Logger().logger
+    triggers = glob.glob(globber)
+    triggers.sort()
+    for file in triggers:
+        log.debug("Executing trigger: %s" % file)
+        try:
+            if file.find(".rpm") != -1:
+                # skip .rpmnew files that may have been installed
+                # in the triggers directory
+                continue
+            if ref:
+                rc = sub_process.call([file, ref], shell=False)
+            else:
+                rc = sub_process.call([file], shell=False)
+        except:
+            log.warning("Warning: failed to execute trigger: %s" % file)
+            continue
+
+        if rc != 0:
+            raise codes.CMException, "certmaster trigger failed: %(file)s returns %(code)d" % { "file" : file, "code" : rc }
+
+
+def submit_csr_to_master(csr_file, master_uri, ca_name=''):
+    """"
+    gets us our cert back from the certmaster.wait_for_cert() method
+    takes csr_file as path location and master_uri
+    returns Bool, str(cert), str(ca_cert)
+    """
+
+    fo = open(csr_file)
+    csr = fo.read()
+    s = xmlrpclib.ServerProxy(master_uri)
 
 
-              
+    # print "DEBUG: waiting for cert"
+    return s.wait_for_cert(csr,ca_name)