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