add a excepthook handler for uncaught exceptions, so they get written to the log
[certmaster.git] / certmaster / certmaster.py
old mode 100755 (executable)
new mode 100644 (file)
index 1bf3a2d..506a029
@@ -16,7 +16,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 # standard modules
 import SimpleXMLRPCServer
+import string
 import sys
+import traceback
 import os
 import os.path
 from OpenSSL import crypto
@@ -90,7 +92,7 @@ class CertMaster(object):
         commonname = commonname.replace('\\', '')       
         return commonname
     
-    def wait_for_cert(self, csrbuf):
+    def wait_for_cert(self, csrbuf, with_triggers=True):
         """
            takes csr as a string
            returns True, caller_cert, ca_cert
@@ -104,7 +106,9 @@ class CertMaster(object):
             return False, '', ''
             
         requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
-        
+
+        if with_triggers:
+            self._run_triggers(requesting_host, '/var/lib/certmaster/triggers/request/pre/*') 
 
         self.logger.info("%s requested signing of cert %s" % (requesting_host,csrreq.get_subject().CN))
         # get rid of dodgy characters in the filename we're about to make
@@ -137,6 +141,8 @@ class CertMaster(object):
             slavecert = certs.retrieve_cert_from_file(certfile)
             cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, slavecert)
             cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
+            if with_triggers:
+                self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/request/post/*')
             return True, cert_buf, cacert_buf
         
         # if we don't have a cert then:
@@ -149,6 +155,8 @@ class CertMaster(object):
             cert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
             cacert_buf = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cacert)
             self.logger.info("cert for %s was autosigned" % (requesting_host))
+            if with_triggers:
+                self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*')
             return True, cert_buf, cacert_buf
         
         else:
@@ -158,6 +166,8 @@ class CertMaster(object):
             destfo.close()
             del destfo
             self.logger.info("cert for %s created and ready to be signed" % (requesting_host))
+            if with_triggers:
+                self._run_triggers(None,'/var/lib/certmaster/triggers/request/post/*')
             return False, '', ''
 
         return False, '', ''
@@ -172,7 +182,7 @@ class CertMaster(object):
             hosts.append(hn)
         return hosts
    
-    def remove_this_cert(self, hn):
+    def remove_this_cert(self, hn, with_triggers=True):
         """ removes cert for hostname using unlink """
         cm = self
         csrglob = '%s/%s.csr' % (cm.cfg.csrroot, hn)
@@ -183,12 +193,16 @@ class CertMaster(object):
             # FIXME: should be an exception?
             print 'No match for %s to clean up' % hn
             return
+        if with_triggers:
+            self._run_triggers(hn,'/var/lib/certmaster/triggers/remove/pre/*')
         for fn in csrs + certs:
             print 'Cleaning out %s for host matching %s' % (fn, hn)
             self.logger.info('Cleaning out %s for host matching %s' % (fn, hn))
-            os.unlink(fn)         
+            os.unlink(fn)
+        if with_triggers:
+            self._run_triggers(hn,'/var/lib/certmaster/triggers/remove/post/*')
             
-    def sign_this_csr(self, csr):
+    def sign_this_csr(self, csr, with_triggers=True):
         """returns the path to the signed cert file"""
         csr_unlink_file = None
 
@@ -217,7 +231,11 @@ class CertMaster(object):
             csrreq = csr
 
 
-        requesting_host = self._sanitize_cn(csrreq.get_subject().CN)        
+        requesting_host = self._sanitize_cn(csrreq.get_subject().CN)
+        if with_triggers:
+            self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/sign/pre/*')
+
+
         certfile = '%s/%s.cert' % (self.cfg.certroot, requesting_host)
         self.logger.info("Signing for csr %s requested" % certfile)
         thiscert = certs.create_slave_certificate(csrreq, self.cakey, self.cacert, self.cfg.cadir)
@@ -228,13 +246,58 @@ class CertMaster(object):
         del destfo
 
 
-        self.logger.info("csr %s signed" % (certfile)) 
+        self.logger.info("csr %s signed" % (certfile))
+        if with_triggers:
+            self._run_triggers(requesting_host,'/var/lib/certmaster/triggers/sign/post/*')
+
+        
         if csr_unlink_file and os.path.exists(csr_unlink_file):
             os.unlink(csr_unlink_file)
             
         return certfile
+
+    # return a list of already signed certs
+    def get_signed_certs(self, hostglobs=None):
+        certglob = "%s/*.cert" % (self.cfg.certroot)
+
+        certs = []
+        globs = "*"
+        if hostglobs:
+            globs = hostglobs
+
+        for hostglob in globs:
+            certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob)
+            certs = certs + glob.glob(certglob)
+
+        signed_certs = []
+        for cert in certs:
+            # just want the hostname, so strip off path and ext
+            signed_certs.append(os.path.basename(cert).split(".cert", 1)[0])
+
+        return signed_certs
+
+    # return a list of the cert hash string we use to identify systems
+    def get_cert_hashes(self, hostglobs=None):
+        certglob = "%s/*.cert" % (self.cfg.certroot)
+
+        certfiles = []
+        globs = "*"
+        if hostglobs:
+            globs = hostglobs
+
+        for hostglob in globs:
+            certglob = "%s/%s.cert" % (self.cfg.certroot, hostglob)
+            certfiles = certfiles + glob.glob(certglob)
         
+        cert_hashes = []
+        for certfile in certfiles:
+            cert = certs.retrieve_cert_from_file(certfile)
+            cert_hashes.append("%s-%s" % (cert.get_subject().CN, cert.subject_name_hash()))
+            
+        return cert_hashes
 
+    def _run_triggers(self, ref, globber):
+        return utils.run_triggers(ref, globber)
 
 
 class CertmasterXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
@@ -250,16 +313,36 @@ def serve(xmlrpcinstance):
     """
 
 
-    server = CertmasterXMLRPCServer((xmlrpcinstance.cfg.listen_addr, CERTMASTER_LISTEN_PORT))
+    config = read_config(CERTMASTER_CONFIG, CMConfig)
+    listen_addr = config.listen_addr
+    listen_port = config.listen_port
+    if listen_port == '':
+        listen_port = CERTMASTER_LISTEN_PORT 
+    server = CertmasterXMLRPCServer((listen_addr,listen_port))
     server.logRequests = 0 # don't print stuff to console
     server.register_instance(xmlrpcinstance)
     xmlrpcinstance.logger.info("certmaster started")
     xmlrpcinstance.audit_logger.logger.info("certmaster started")
     server.serve_forever()
 
+def excepthook(exctype, value, tracebackobj):
+    exctype_blurb = "Exception occured: %s" % exctype
+    excvalue_blurb = "Exception value: %s" % value
+    exctb_blurb = "Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tracebackobj)))
+
+    print exctype_blurb
+    print excvalue_blurb
+    print exctb_blurb
+
+    log = logger.Logger().logger 
+    log.info(exctype_blurb)
+    log.info(excvalue_blurb)
+    log.info(exctb_blurb)
+
 
 def main(argv):
-    
+   
+    sys.excepthook = excepthook  
     cm = CertMaster('/etc/certmaster/certmaster.conf')
 
     if "daemon" in argv or "--daemon" in argv: