crl-manager.py moved in /usr/bin
[threegates.git] / raw / root / usr / bin / crl-manager.py
1 #! /usr/bin/env python
2 import os
3 import time
4 import popen2
5 import sys
6 import filecmp
7 import shutil
8 import fcntl
9 import re
10 import time
11 import zlib
12 import httplib
13 import threading, select
14
15 CRLURINEW  = "/var/lib/3gates/crlurinew"
16 CRLURI     = "/var/lib/3gates/crluri"
17 CRL        = "/var/lib/3gates/crl"
18 CA_PATH    = "/var/lib/3gates/ca"
19 CRLCHKTIME = 1800.0
20
21 start_time = 0
22
23 # hash of the CA:
24 #   openssl crl -in CRL15 -inform der -hash -noout
25 #
26 # verify of the CRL:
27 #   openssl crl -in CRL15 -inform der -CApath /etc/apache2/ssl/ca/ -noout
28
29
30 #
31 #  Functions
32 #
33
34 class command_output_processor(threading.Thread):
35     def __init__(self, subproc, sout, serr):
36         threading.Thread.__init__(self)        
37
38         self.lock = threading.Lock()
39         if sout:
40             self.sout = ''
41         else:
42             self.sout = False
43         if serr:
44             self.serr = ''
45         else:
46             self.serr = False
47             
48         ctrlrd, ctrlwr = os.pipe()
49         self.ctrlrd = os.fdopen(ctrlrd, "rb")
50         self.ctrlwr = os.fdopen(ctrlwr, "wb")
51         self.shutdown = True
52         self.output = subproc.fromchild
53         self.error = subproc.childerr
54
55     def run(self):
56         self.lock.acquire()
57         self.shutdown = False
58         self.lock.release()
59         
60         while (not self.shutdown) and ((not self.output.closed) or (not self.error.closed)):
61             # print "main loop"
62             intrig = [ self.ctrlrd ]
63             if not self.output.closed:
64                 intrig.append(self.output)
65                 
66             if not self.error.closed:
67                 intrig.append(self.error)
68
69             # print "pre select"
70             retsel = select.select( intrig, [ ], [ ], 5)
71             # print "post select"
72
73             if len(retsel[0]) == 0:
74                 # print "no data"
75                 continue
76             
77             # print "len range", len(retsel[0])
78             for i in range(0,len(retsel[0])):
79                 # print "LOOP %d: %d\n" % (i, len(retsel[0]))
80                 if retsel[0][i] == self.ctrlrd:
81                     self.ctrlrd.read()
82                     
83                 elif retsel[0][i] == self.output:
84                     data = self.output.read()
85                     
86                     if (len(data) == 0):
87                         self.output.close()
88                     else:
89                         pass # print "Stdout data len", len(data)
90
91                     if self.sout != False:
92                         self.sout += data
93                     
94                 elif retsel[0][i] == self.error:
95                     data = self.error.read()
96                     if (len(data) == 0):
97                         self.error.close()
98                     else:
99                         pass # print "Stderr data len", len(data)
100
101                     if self.serr != False:
102                         self.serr += data
103
104         self.lock.acquire()
105         try:
106             if not self.ctrlrd.closed:
107                 self.ctrlrd.close()
108             if not self.ctrlwr.closed:
109                 self.ctrlwr.close()
110         except:
111             print "exception at ctrl close"
112             pass
113         
114         self.shutdown = True
115         self.lock.release()
116
117                 
118     def stop(self):
119         self.lock.acquire()
120         if not self.shutdown:
121             self.ctrlwr.write("w")
122             self.shutdown = True
123         self.lock.release()
124         
125
126 def execute_command(cmd, input, with_output, with_error, wait):
127     subret = -1
128     subproc = popen2.Popen3(cmd, True)
129
130     th = command_output_processor(subproc, with_output, with_error)
131     th.start()
132     
133     if input:
134         try:
135             poin = subproc.tochild
136             poin.write(input)
137             poin.close()
138         except Exception, e:
139             print "exception at poin close", e
140             pass
141             
142     for i in range(1, wait * 10):
143         # print "loop ", i
144         subret = subproc.poll()
145         if (subret != -1):
146             break
147         time.sleep(0.1)
148     else:
149         # FIXME: kill procedure here
150         print "NON ESCE"
151     print "prima della stop ", subret
152     th.stop()
153     ret = th.join()
154
155     return (subret, th.sout, th.serr)
156     
157     
158
159
160 def crlurinew_mustupdate(crluri, crlurinew):
161     ldir = os.listdir(crlurinew)
162     for cf in ldir:
163         # find all new .cont files 
164         if re.match("^[a-f0-9]{8}\.cont$", cf) == None:
165             continue
166         # TODO verify if the name it's really the hash of the content (anti-tampering)
167         
168         acc = os.access(crluri+"/"+cf, os.F_OK | os.R_OK | os.W_OK)
169
170         if (acc == False):
171             return True
172
173     return False
174
175 def crluri_fromnew(crluri, crlurinew):
176     ldir = os.listdir(crlurinew)
177     for cf in ldir:
178         # find all new .cont files 
179         if re.match("^[a-f0-9]{8}\.cont$", cf) == None:
180             continue
181         # TODO verify if the name it's really the hash of the content (anti-tampering)
182
183         acc = os.access(crluri+"/"+cf, os.F_OK | os.R_OK | os.W_OK)
184
185         if (acc == False):
186             print("copy file [%s]\n  from dir:[%s]\n  to dir:  [%s]" % (cf, crlurinew, crluri))
187             shutil.copyfile(crlurinew+"/"+cf, crluri+"/"+cf)
188         else:
189             print "WARNING: File %s just exists in %s." % (cf, crluri)
190
191         os.remove(crlurinew+"/"+cf)
192         
193     return True
194
195 def ssltime2int(s):
196     sout = re.sub('^.*= *', '', s).strip("\n")
197     t = time.strptime(sout, "%b %d %H:%M:%S %Y %Z")
198     ts = int(time.strftime("%s", t))
199     return ts
200
201 #
202 #  Se non esiste l'expire o se esiste ed in scadenza rende True, altrimenti False
203 #
204 def crl_needverify(cu_name, crl, exptime, lcrldir):
205     c_name = cu_name.replace(".cont", ".crl")
206
207     acc = os.access(crl+"/"+c_name, os.F_OK | os.R_OK | os.W_OK)
208     if acc == False:
209         return (True, None)
210
211     for cfcrl in lcrldir:
212         if re.match( ("^[0-9]+\.%s$" % c_name), cfcrl) != None:
213             if ((float(re.sub("\.[a-f0-9]{8}\.crl$", "", cfcrl)) - time.time()) < exptime):
214                 print "    expire link expired"
215                 return (True, cfcrl)
216             break
217     else:
218         print "    expire link not found"
219         return (True, None)
220
221     # print "    expire link verified"
222     return (False, None)
223
224 def crl_update(contfile, crluripath, crlpath, expiresym):
225     ret = False
226
227     crl_file = re.sub("\.cont$", ".crl", contfile)
228     print "crl_update contfile: %s  crl file %s" % (contfile, crl_file)
229
230     f = file(crluripath+"/"+contfile, "r")
231
232     
233     # FIMXME: modo migliore di looppare ?
234     while True:
235         s = f.readline(1024)
236         if s == '':
237             break
238
239         uri = s.strip("\n")
240
241         # verify proto
242         if (re.match("^[hH][tT][tT][pP]://", uri) != None):
243             # get http file
244             uri = re.sub("^[hH][tT][tT][pP]://", "", uri)
245             site = re.sub("/.*", "", uri)
246             page = re.sub("^[^/]*/", "/", uri)
247
248             print "HTTP URI [%s]  HOST [%s]  PAGE [%s]" % (uri, site, page)
249             conn = httplib.HTTPConnection(site)
250             conn.request("GET", page)
251             r1 = conn.getresponse()
252             if r1.status != 200:
253                 continue
254             
255             crl_data = r1.read()
256             conn.close()
257             break
258
259         elif (re.match("^[lL][dD][aA][pP]://", uri) != None):
260             print "LDAP Path: [%s]" % uri
261             # get ldap file
262             continue
263
264     f.close()
265         
266     # verify the crl
267     #   openssl crl -inform der -CApath /etc/apache2/ssl/ca/ -noout
268     # print "execute 1"
269     subret, sout, serr = execute_command("/usr/bin/openssl crl -inform pem -CApath %s -noout" % CA_PATH ,
270                              crl_data, False, False, 20)
271     if (subret != 0):
272         # print "execute 2"
273         subret, sout, serr = execute_command("/usr/bin/openssl crl -inform der -CApath %s -noout" % CA_PATH ,
274                                  crl_data, False, False, 20)
275         
276         if (subret != 0):
277             print "CRL verify FAILED!"
278             return False
279         else:
280             print "CRL verified!"
281         
282
283         sout = ""
284         # print "execute 3"
285         subret, sout, serr = execute_command("/usr/bin/openssl crl -inform der -outform pem" ,
286                                              crl_data, True, False, 20)
287         crl_data = sout
288
289
290     # verify that lastupdate was in the past
291     subret, sout, serr = execute_command("/usr/bin/openssl crl -inform pem -lastupdate -noout",
292                                          crl_data, True, False, 20)
293
294     print "lastupdate:  [%s]" % sout
295     ts_last = ssltime2int(sout)
296     print "lastupdate2: [%d]" % ts_last
297     print "start_time:  [%d]" % start_time 
298     if ts_last > start_time:
299         print "ERROR: lastupdate is in the future"
300         return False
301
302     # verify that nextupdate was in the past
303     subret, sout, serr = execute_command("/usr/bin/openssl crl -inform pem -nextupdate -noout",
304                                          crl_data, True, False, 20)
305
306     ts_next = ssltime2int(sout)
307     print "TS_NEXT: ", ts_next
308     if ts_next < start_time:
309         print "WARNING: nextupdate is in the past"
310             
311     # verify if exists an old copy of the crl and if is the same
312     acc = os.access(CRL+"/"+crl_file, os.F_OK | os.R_OK | os.W_OK)
313     if acc != False:
314         sout = ""
315         print "CRL verify:"
316
317         print "execute 4: /usr/bin/openssl crl -inform pem -in %s -noout -fingerprint" % (CRL+"/"+crl_file)
318         subret, finger_old, serr = execute_command("/usr/bin/openssl crl -inform pem -in %s -noout -fingerprint" % (CRL+"/"+crl_file),
319                                                    False, True, False, 20)
320
321         if (subret != 0):
322             print "acquiring CRL fingerprint FAILED!", subret
323             return False
324
325         subret, finger_new, serr = execute_command("/usr/bin/openssl crl -inform pem -noout -fingerprint",
326                                                    crl_data, True, False, 20)
327
328         if finger_old == finger_new:
329             return True
330
331
332     # update the crl file
333     print "UPDATE::save new file to: ", crl_file
334     newf = file(CRL+"/"+crl_file+"_new", "w")
335     newf.write(crl_data)
336     newf.close()
337     os.rename(CRL+"/"+crl_file+"_new", CRL+"/"+crl_file)
338
339     print "UPDATE:expiretime file:", expiresym
340     # update the expiretime symlink
341     if expiresym != None:
342         os.unlink(CRL+"/"+expiresym)
343         
344     print "UPDATE:symlinking(%s, %s)" % (CRL+"/"+crl_file, CRL+"/"+str(ts_next)+"."+crl_file)
345     oldcwd = os.getcwd()
346     os.chdir(CRL) 
347     os.symlink(crl_file, str(ts_next)+"."+crl_file)
348     os.chdir(oldcwd)
349
350     subret, crl_hash, serr = execute_command("/usr/bin/openssl crl -inform pem -noout -hash",
351                                                crl_data, True, False, 20)
352
353     crl_hash = crl_hash.strip("\n")
354     print "hash: [%s]" % crl_hash
355
356     new_st = os.stat(CRL+"/"+crl_file)
357     print "NEW_ST: ", new_st.st_ino
358
359     lcrldir = os.listdir(crlpath)
360     print "lcrldir: ", lcrldir
361     print "preemp: ", [ x for x in lcrldir if re.match(crl_hash+".r[0-9]+", x) ]
362     for crl_cur in [ x for x in lcrldir if re.match(crl_hash+".r[0-9]+", x) ]:
363         st = os.stat(crlpath+"/"+crl_cur)
364         print "CMP: ", st.st_ino
365         if new_st.st_ino == st.st_ino:
366             break
367     else:
368         for i in range(0, 1000):
369             acc = os.access(CRL+"/"+crl_hash+".r"+str(i), os.F_OK | os.R_OK | os.W_OK)
370             if acc == False:
371                 oldcwd = os.getcwd()
372                 os.chdir(CRL) 
373                 os.symlink(crl_file, crl_hash+".r"+str(i))
374                 os.chdir(oldcwd)
375                 break
376     
377         
378     return ret
379     
380 #
381 #  MAIN
382 #
383 if __name__=='__main__':
384
385     start_time = time.time()
386     #
387     #  VERIFY
388     #
389     # print "\n--- VERIFY ---\n"
390     update = False
391     old_umask = os.umask(0002)
392     fl = file(CRLURINEW + "/certgate.lck", "w+")
393     os.umask(old_umask)
394     # print "lock SH"
395     fcntl.flock(fl, fcntl.LOCK_SH)
396
397
398     while True:
399         # verify for new .cont files
400         update = crlurinew_mustupdate(CRLURI, CRLURINEW)
401         if update == True:
402             break
403         
404         ldir = os.listdir(CRLURI)
405         lcrldir = os.listdir(CRL)
406         for cf in ldir:
407             # for each .cont file verify existence of associated .crl file and it's expire time
408             if re.match("[a-f0-9]{8}\.cont$", cf) != None:
409                 need, expiresym = crl_needverify(cf, CRL, CRLCHKTIME, lcrldir)
410                 if need:
411                     update = True
412                     break
413
414         # end of while True
415         break
416     
417     fcntl.flock(fl, fcntl.LOCK_UN)
418     # print "unlock SH"
419
420     if update == False:
421         sys.exit(0)
422
423     #
424     #  UPDATE 
425     #
426     #  We lock in EX mode before continue.
427     #
428     print "\n--- UPDATE ---\n"
429     print "lock EX"
430     fcntl.flock(fl, fcntl.LOCK_EX)
431
432     # try to copy new crluri from DMZ dir to working dir
433     crluri_fromnew(CRLURI, CRLURINEW)
434
435     ldir = os.listdir(CRLURI)
436     lcrldir = os.listdir(CRL)
437     for cf in ldir:
438         # if .cont file verify existance of associated .crl file
439         if re.match("^[a-f0-9]{8}\.cont$", cf) != None:
440             need, expiresym = crl_needverify(cf, CRL, CRLCHKTIME, lcrldir)
441             if need:
442                 # download and check update
443                 crl_update(cf, CRLURI, CRL, expiresym)
444                 
445
446     fcntl.flock(fl, fcntl.LOCK_UN)
447     print "unlock EX"
448     
449     fl.close()
450     sys.exit(111)
451     
452 #     while True:
453 #         s = f.readline(1024)
454 #         if s == '':
455 #             break
456 #         print s
457 #         print len(s)
458
459
460 #     f.close()
461
462     exit 
463
464 #    main()
465