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