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