5544b05e981c0a75518e978c662020d203214218
[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(0)
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. It also hardcodes the port number
111 # for the certmaster for now
112 hostname = None
113 hostname = socket.gethostname()
114 # print "DEBUG: HOSTNAME TRY1: %s" % hostname
115 try:
116 ip = socket.gethostbyname(hostname)
117 # print "DEBUG: IP TRY2: %s" % ip
118 except:
119 # print "DEBUG: ERROR: returning"
120 return hostname
121 if ip != "127.0.0.1":
122 # print "DEBUG: ERROR: returning 2"
123 return hostname
124
125 if talk_to_certmaster:
126 config_file = '/etc/certmaster/minion.conf'
127 config = read_config(config_file, MinionConfig)
128
129 server = config.certmaster
130 port = 51235
131
132 try:
133 s = socket.socket()
134 s.settimeout(5)
135 s.connect((server, port))
136 (intf, port) = s.getsockname()
137 remote_hostname = socket.gethostbyaddr(intf)[0]
138 if remote_hostname != "localhost":
139 hostname = remote_hostname
140 # print "DEBUG: HOSTNAME FROM CERTMASTER == %s" % hostname
141 s.close()
142 except:
143 s.close()
144 raise
145
146 # print "DEBUG: final hostname=%s" % hostname
147 return hostname
148
149
150 # FIXME: move to requestor module and also create a verbose mode
151 # prints to the screen for usage by /usr/bin/certmaster-request
152
153 def create_minion_keys():
154 # FIXME: paths should not be hard coded here, move to settings universally
155 config_file = '/etc/certmaster/minion.conf'
156 config = read_config(config_file, MinionConfig)
157 cert_dir = config.cert_dir
158 master_uri = 'http://%s:51235/' % config.certmaster
159 # print "DEBUG: acquiring hostname"
160 hn = get_hostname()
161 # print "DEBUG: hostname = %s\n" % hn
162
163 if hn is None:
164 raise codes.CMException("Could not determine a hostname other than localhost")
165
166 key_file = '%s/%s.pem' % (cert_dir, hn)
167 csr_file = '%s/%s.csr' % (cert_dir, hn)
168 cert_file = '%s/%s.cert' % (cert_dir, hn)
169 ca_cert_file = '%s/ca.cert' % cert_dir
170
171
172 if os.path.exists(cert_file) and os.path.exists(ca_cert_file):
173 # print "DEBUG: err, no cert_file"
174 return
175
176 keypair = None
177 try:
178 if not os.path.exists(cert_dir):
179 os.makedirs(cert_dir)
180 if not os.path.exists(key_file):
181 keypair = certs.make_keypair(dest=key_file)
182 if not os.path.exists(csr_file):
183 if not keypair:
184 keypair = certs.retrieve_key_from_file(key_file)
185 csr = certs.make_csr(keypair, dest=csr_file)
186 except Exception, e:
187 traceback.print_exc()
188 raise codes.CMException, "Could not create local keypair or csr for session"
189
190 result = False
191 log = logger.Logger().logger
192 while not result:
193 try:
194 # print "DEBUG: submitting CSR to certmaster: %s" % master_uri
195 log.debug("submitting CSR to certmaster %s" % master_uri)
196 result, cert_string, ca_cert_string = submit_csr_to_master(csr_file, master_uri)
197 except socket.gaierror, e:
198 raise codes.CMException, "Could not locate certmaster at %s" % master_uri
199
200 # logging here would be nice
201 if not result:
202 # print "DEBUG: no response from certmaster, sleeping 10 seconds"
203 log.warning("no response from certmaster %s, sleeping 10 seconds" % master_uri)
204 time.sleep(10)
205
206
207 if result:
208 # print "DEBUG: recieved certificate from certmaster"
209 log.debug("received certificate from certmaster %s, storing to %s" % (master_uri, cert_file))
210 cert_fd = os.open(cert_file, os.O_RDWR|os.O_CREAT, 0644)
211 os.write(cert_fd, cert_string)
212 os.close(cert_fd)
213
214 ca_cert_fd = os.open(ca_cert_file, os.O_RDWR|os.O_CREAT, 0644)
215 os.write(ca_cert_fd, ca_cert_string)
216 os.close(ca_cert_fd)
217
218 def run_triggers(ref, globber):
219 """
220 Runs all the trigger scripts in a given directory.
221 ref can be a certmaster object, if not None, the name will be passed
222 to the script. If ref is None, the script will be called with
223 no argumenets. Globber is a wildcard expression indicating which
224 triggers to run. Example: "/var/lib/certmaster/triggers/blah/*"
225 """
226
227 log = logger.Logger().logger
228 triggers = glob.glob(globber)
229 triggers.sort()
230 for file in triggers:
231 log.debug("Executing trigger: %s" % file)
232 try:
233 if file.find(".rpm") != -1:
234 # skip .rpmnew files that may have been installed
235 # in the triggers directory
236 continue
237 if ref:
238 rc = sub_process.call([file, ref], shell=False)
239 else:
240 rc = sub_process.call([file], shell=False)
241 except:
242 log.warning("Warning: failed to execute trigger: %s" % file)
243 continue
244
245 if rc != 0:
246 raise codes.CMException, "certmaster trigger failed: %(file)s returns %(code)d" % { "file" : file, "code" : rc }
247
248
249 def submit_csr_to_master(csr_file, master_uri):
250 """"
251 gets us our cert back from the certmaster.wait_for_cert() method
252 takes csr_file as path location and master_uri
253 returns Bool, str(cert), str(ca_cert)
254 """
255
256 fo = open(csr_file)
257 csr = fo.read()
258 s = xmlrpclib.ServerProxy(master_uri)
259
260 # print "DEBUG: waiting for cert"
261 return s.wait_for_cert(csr)
262