BATS fell down pushing a process into the background, so I switched to shunit2 /...
[certmaster.git] / certmaster / utils.py
1 """
2 Copyright 2007-2008, Red Hat, Inc
3 see AUTHORS
4
5 This software may be freely redistributed under the terms of the GNU
6 general public license.
7
8 You should have received a copy of the GNU General Public License
9 along with this program; if not, write to the Free Software
10 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
11 """
12
13 import os
14 import string
15 import sys
16 import traceback
17 import xmlrpclib
18 import socket
19 import time
20 import glob
21
22 import codes
23 import certs
24 from config import read_config
25 from commonconfig import MinionConfig
26 import logger
27 import sub_process
28
29 # FIXME: module needs better pydoc
30
31 # FIXME: can remove this constant?
32 REMOTE_ERROR = "REMOTE_ERROR"
33
34 # The standard I/O file descriptors are redirected to /dev/null by default.
35 if (hasattr(os, "devnull")):
36 REDIRECT_TO = os.devnull
37 else:
38 REDIRECT_TO = "/dev/null"
39
40 def trace_me():
41 x = traceback.extract_stack()
42 bar = string.join(traceback.format_list(x))
43 return bar
44
45 def daemonize(pidfile=None):
46 """
47 Daemonize this process with the UNIX double-fork trick.
48 Writes the new PID to the provided file name if not None.
49 """
50
51 pid = os.fork()
52 if pid > 0:
53 sys.exit(0)
54 os.chdir("/")
55 os.setsid()
56 os.umask(077)
57 pid = os.fork()
58
59 os.close(0)
60 os.close(1)
61 os.close(2)
62
63 # based on http://code.activestate.com/recipes/278731/
64 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
65
66 os.dup2(0, 1) # standard output (1)
67 os.dup2(0, 2) # standard error (2)
68
69
70
71 if pid > 0:
72 if pidfile is not None:
73 open(pidfile, "w").write(str(pid))
74 sys.exit(0)
75
76
77 def nice_exception(etype, evalue, etb):
78 etype = str(etype)
79 try:
80 lefti = etype.index("'") + 1
81 righti = etype.rindex("'")
82 nicetype = etype[lefti:righti]
83 except:
84 nicetype = etype
85 nicestack = string.join(traceback.format_list(traceback.extract_tb(etb)))
86 return [ REMOTE_ERROR, nicetype, str(evalue), nicestack ]
87
88 def is_error(result):
89 # FIXME: I believe we can remove this function
90 if type(result) != list:
91 return False
92 if len(result) == 0:
93 return False
94 if result[0] == REMOTE_ERROR:
95 return True
96 return False
97
98 def get_hostname(talk_to_certmaster=True):
99 """
100 "localhost" is a lame hostname to use for a key, so try to get
101 a more meaningful hostname. We do this by connecting to the certmaster
102 and seeing what interface/ip it uses to make that connection, and looking
103 up the hostname for that.
104 """
105 # FIXME: this code ignores http proxies (which granted, we don't
106 # support elsewhere either.
107 hostname = None
108 hostname = socket.gethostname()
109 # print "DEBUG: HOSTNAME TRY1: %s" % hostname
110 try:
111 ip = socket.gethostbyname(hostname)
112 except:
113 return hostname
114 if ip != "127.0.0.1":
115 return hostname
116
117
118 # FIXME: move to requestor module and also create a verbose mode
119 # prints to the screen for usage by /usr/bin/certmaster-request
120
121 def create_minion_keys(hostname=None, ca_name=''):
122 log = logger.Logger().logger
123
124 # FIXME: paths should not be hard coded here, move to settings universally
125 config_file = '/etc/certmaster/minion.conf'
126 config = read_config(config_file, MinionConfig)
127
128 try:
129 certauth=config.ca[ca_name]
130 except:
131 raise codes.CMException("Unknown cert authority: %s" % ca_name)
132
133 cert_dir = certauth.cert_dir
134
135 master_uri = 'http://%s:%s/' % (config.certmaster, config.certmaster_port)
136
137 hn = hostname
138 if hn is None:
139 hn = get_hostname()
140
141 if hn is None:
142 raise codes.CMException("Could not determine a hostname other than localhost")
143 else:
144 # use lowercase letters for hostnames
145 hn = hn.lower()
146
147 key_file = '%s/%s.pem' % (cert_dir, hn)
148 csr_file = '%s/%s.csr' % (cert_dir, hn)
149 cert_file = '%s/%s.cert' % (cert_dir, hn)
150 ca_cert_file = '%s/ca.cert' % cert_dir
151
152 if os.path.exists(cert_file) and os.path.exists(ca_cert_file):
153 # print "DEBUG: err, no cert_file"
154 return
155
156 keypair = None
157 try:
158 if not os.path.exists(cert_dir):
159 os.makedirs(cert_dir)
160 if not os.path.exists(key_file):
161 keypair = certs.make_keypair(dest=key_file)
162 if not os.path.exists(csr_file):
163 if not keypair:
164 keypair = certs.retrieve_key_from_file(key_file)
165 csr = certs.make_csr(keypair, dest=csr_file, hostname=hn)
166 except Exception, e:
167 traceback.print_exc()
168 raise codes.CMException, "Could not create local keypair or csr for session"
169
170 result = False
171
172 while not result:
173 try:
174 # print "DEBUG: submitting CSR to certmaster: %s" % master_uri
175 log.debug("submitting CSR: %s to certmaster %s" % (csr_file, master_uri))
176 result, cert_string, ca_cert_string = submit_csr_to_master(csr_file, master_uri, ca_name)
177 except socket.error, e:
178 log.warning("Could not locate certmaster at %s" % master_uri)
179
180 # logging here would be nice
181 if not result:
182 # print "DEBUG: no response from certmaster, sleeping 10 seconds"
183 log.warning("no response from certmaster %s, sleeping 10 seconds" % master_uri)
184 time.sleep(10)
185
186
187 if result:
188 # print "DEBUG: recieved certificate from certmaster"
189 log.debug("received certificate from certmaster %s, storing to %s" % (master_uri, cert_file))
190 if not keypair:
191 keypair = certs.retrieve_key_from_file(key_file)
192 valid = certs.check_cert_key_match(cert_string, keypair)
193 if not valid:
194 log.info("certificate does not match key (run certmaster-ca --clean first?)")
195 sys.stderr.write("certificate does not match key (run certmaster-ca --clean first?)\n")
196 return
197 cert_fd = os.open(cert_file, os.O_RDWR|os.O_CREAT, 0644)
198 os.write(cert_fd, cert_string)
199 os.close(cert_fd)
200
201 ca_cert_fd = os.open(ca_cert_file, os.O_RDWR|os.O_CREAT, 0644)
202 os.write(ca_cert_fd, ca_cert_string)
203 os.close(ca_cert_fd)
204
205 def run_triggers(ref, globber):
206 """
207 Runs all the trigger scripts in a given directory.
208 ref can be a certmaster object, if not None, the name will be passed
209 to the script. If ref is None, the script will be called with
210 no argumenets. Globber is a wildcard expression indicating which
211 triggers to run. Example: "/var/lib/certmaster/triggers/blah/*"
212 """
213
214 log = logger.Logger().logger
215 triggers = glob.glob(globber)
216 triggers.sort()
217 for file in triggers:
218 log.debug("Executing trigger: %s" % file)
219 try:
220 if file.find(".rpm") != -1:
221 # skip .rpmnew files that may have been installed
222 # in the triggers directory
223 continue
224 if ref:
225 rc = sub_process.call([file, ref], shell=False)
226 else:
227 rc = sub_process.call([file], shell=False)
228 except:
229 log.warning("Warning: failed to execute trigger: %s" % file)
230 continue
231
232 if rc != 0:
233 raise codes.CMException, "certmaster trigger failed: %(file)s returns %(code)d" % { "file" : file, "code" : rc }
234
235
236 def submit_csr_to_master(csr_file, master_uri, ca_name=''):
237 """"
238 gets us our cert back from the certmaster.wait_for_cert() method
239 takes csr_file as path location and master_uri
240 returns Bool, str(cert), str(ca_cert)
241 """
242
243 fo = open(csr_file)
244 csr = fo.read()
245 s = xmlrpclib.ServerProxy(master_uri)
246
247 # print "DEBUG: waiting for cert"
248 return s.wait_for_cert(csr,ca_name)