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