github-1: support for hashing functions other than sha1
[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 warning = '';
172 cert_string = '';
173 ca_cert_string = '';
174
175 while not result:
176 try:
177 # print "DEBUG: submitting CSR to certmaster: %s" % master_uri
178 log.debug("submitting CSR: %s to certmaster %s" % (csr_file, master_uri))
179 result, cert_string, ca_cert_string, warning = submit_csr_to_master(csr_file, master_uri, ca_name)
180 except socket.error, e:
181 log.warning("Could not locate certmaster at %s" % master_uri)
182
183 # logging here would be nice
184 if not result:
185 # print "DEBUG: no response from certmaster, sleeping 10 seconds"
186 log.warning("no response from certmaster %s, sleeping 10 seconds" % master_uri)
187 time.sleep(10)
188
189 if warning != '':
190 log.warning(warning)
191 sys.stderr.write(warning)
192
193 if result:
194 # print "DEBUG: recieved certificate from certmaster"
195 log.debug("received certificate from certmaster %s, storing to %s" % (master_uri, cert_file))
196 if not keypair:
197 keypair = certs.retrieve_key_from_file(key_file)
198 valid = certs.check_cert_key_match(cert_string, keypair)
199 if not valid:
200 if ca_name != "":
201 ca_suffix = "--ca " + ca_name
202 else:
203 ca_suffix = ""
204 log.info("certificate does not match key (run certmaster-ca --clean %s first on the certmaster ?)" % ca_suffix )
205 sys.stderr.write("certificate does not match key (run certmaster-ca --clean %s first on the certmaster ?)\n" % ca_suffix)
206 return
207 cert_fd = os.open(cert_file, os.O_RDWR|os.O_CREAT, 0644)
208 os.write(cert_fd, cert_string)
209 os.close(cert_fd)
210
211 ca_cert_fd = os.open(ca_cert_file, os.O_RDWR|os.O_CREAT, 0644)
212 os.write(ca_cert_fd, ca_cert_string)
213 os.close(ca_cert_fd)
214
215 def run_triggers(ref, globber):
216 """
217 Runs all the trigger scripts in a given directory.
218 ref can be a certmaster object, if not None, the name will be passed
219 to the script. If ref is None, the script will be called with
220 no argumenets. Globber is a wildcard expression indicating which
221 triggers to run. Example: "/var/lib/certmaster/triggers/blah/*"
222 """
223
224 log = logger.Logger().logger
225 triggers = glob.glob(globber)
226 triggers.sort()
227 for file in triggers:
228 log.debug("Executing trigger: %s" % file)
229 try:
230 if file.find(".rpm") != -1:
231 # skip .rpmnew files that may have been installed
232 # in the triggers directory
233 continue
234 if ref:
235 rc = sub_process.call([file, ref], shell=False)
236 else:
237 rc = sub_process.call([file], shell=False)
238 except:
239 log.warning("Warning: failed to execute trigger: %s" % file)
240 continue
241
242 if rc != 0:
243 raise codes.CMException, "certmaster trigger failed: %(file)s returns %(code)d" % { "file" : file, "code" : rc }
244
245
246 def submit_csr_to_master(csr_file, master_uri, ca_name=''):
247 """"
248 gets us our cert back from the certmaster.wait_for_cert() method
249 takes csr_file as path location and master_uri
250 returns Bool, str(cert), str(ca_cert), str(warning)
251 """
252
253 fo = open(csr_file)
254 csr = fo.read()
255 s = xmlrpclib.ServerProxy(master_uri)
256
257 # print "DEBUG: waiting for cert"
258 return s.wait_for_cert(csr,ca_name)