5
(c) by Michael Stroeder, michael@stroeder.com
7
This script is intended to handle the confirmation mail for a
9
It receives the mail on stdin and moves the certificate request file
10
from pend_reqs_dir to new_reqs_dir. If the senders from:
11
address is different of the one in the certificate request the sender
12
is notified about this by e-mail.
13
If an error occurs the exit code is still zero to prevent the
14
mail system from generating a bounce revealing internal informations.
19
import sys, os, shutil, time, string, smtplib, rfc822, getopt
20
from mimify import mime_decode_header, mime_encode_header
22
# Einen Datensatz in Protokolldatei schreiben
23
# log Filehandle von bereits geoeffneter Protokolldatei
24
# Kategorie des Eintrags, z.B. 'Error:'
27
def LogWrite(log,Kategorie,Mail,Kommentar):
28
log.write('%s %s: ' % (time.strftime('%d.%m.%Y %X',time.localtime(time.time())),Kategorie))
30
for i in ['from','subject','message-id']:
32
log.write('%s ' % (Mail[i]))
33
log.write('%s\n' % (Kommentar))
36
def findoption(options,paramname):
43
########################################################################
45
########################################################################
47
script_name=sys.argv[0]
50
# The log file can only be stderr as long as the config is not read
53
# Parse command-line options
55
options,args=getopt.getopt(sys.argv[1:],'h',['help','config=','pycalib='])
56
except getopt.error,e:
57
LogWrite(logfile,'Error',None,str(e))
59
# Try to find modules directory
60
if findoption(options,'--pycalib')!=():
61
pycalib = findoption(options,'--pycalib')[1]
63
pycalib = os.environ.get('PYCALIB','/usr/local/pyca/pylib')
64
if not os.path.exists(pycalib) or not os.path.isdir(pycalib):
65
LogWrite(logfile,'Error',None,'Module directory %s not exists or not a directory.' % (pycalib))
66
sys.path.append(pycalib)
68
if findoption(options,'--config')!=():
69
opensslcnfname = findoption(options,'--config')[1]
71
opensslcnfname = os.environ.get('OPENSSL_CONF','/etc/openssl/openssl.cnf')
73
if not os.path.isfile(opensslcnfname):
74
LogWrite(logfile,'Error',None,'Config file %s not found.' % (opensslcnfname))
76
import openssl,charset
77
from openssl.db import \
79
DB_type,DB_exp_date,DB_rev_date,DB_serial,DB_file,DB_name,DB_number, \
80
DB_TYPE_REV,DB_TYPE_EXP,DB_TYPE_VAL, \
81
dbtime2tuple,GetEntriesbyDN,SplitDN
83
# Read the configuration file
84
if os.path.isfile('%s.pickle' % (opensslcnfname)):
85
# Try to read OpenSSL's config file from a pickled copy
86
f=open('%s.pickle' % (opensslcnfname),'rb')
88
# first try to use the faster cPickle module
89
from cPickle import load
91
from pickle import load
95
# Parse OpenSSL's config file from source
96
opensslcnf=openssl.cnf.OpenSSLConfigClass(opensslcnfname)
98
pyca_section = opensslcnf.data.get('pyca',{})
100
logfile_name = pyca_section.get('caCertConfirmReqLog','/var/log/pyca/ca-certreq-mail.out')
101
logfile = open(logfile_name,'a')
103
openssl.bin_filename = pyca_section.get('OpenSSLExec','/usr/local/ssl/bin/openssl')
104
if not os.path.isfile(openssl.bin_filename):
105
LogWrite(logfile,'Error',None,'Did not find OpenSSL executable %s.' % (openssl.bin_filename))
107
ca_names = opensslcnf.sectionkeys.get('ca',[])
108
MailRelay = pyca_section.get('MailRelay','localhost')
109
caCertReqMailAdr = pyca_section.get('caCertReqMailAdr','')
110
caInternalCertTypes = pyca_section.get('caInternalCertTypes',[])
111
caInternalDomains = pyca_section.get('caInternalDomains','')
112
if type(caInternalDomains)!=type([]):
113
caInternalDomains = [caInternalDomains]
115
#############################################################
117
#############################################################
119
m=rfc822.Message(sys.stdin)
121
#############################################################
122
# Format von Subject und Body ueberpruefen
123
# Wirkt recht paranoid, soll aber gegen Muellmails schuetzen
124
#############################################################
126
# Ueberlange Subjects verbieten
127
if len(m["subject"])>80:
128
LogWrite(logfile,'Error',m,'Subject too long.')
131
if m.has_key('from'):
132
from_addr = mime_decode_header(string.strip(m["from"]))
133
from_name, from_mail = rfc822.AddressList(from_addr).addresslist[0]
137
subject = string.strip(m["subject"])
138
subjectstart = string.find(subject,'cert-req-')
142
prefix,ca_name,caChallengeId = string.split(subject[subjectstart:len(subject)],'.',2)
144
LogWrite(logfile,'Error',m,'Subject has wrong format.')
148
if prefix=='cert-req-SPKAC':
149
request_filenamesuffix = 'spkac'
150
elif prefix=='cert-req-PKCS10':
151
request_filenamesuffix = 'pem'
153
LogWrite(logfile,'Error',m,'Subject has wrong format.')
156
# ChallengeID nicht zu lang?
157
if len(caChallengeId)>30:
158
LogWrite(logfile,'Error',m,'caChallengeId %s has bad format.' % (caChallengeId))
162
if not (ca_name in ca_names):
163
LogWrite(logfile,'Error',m,'ca_name "%s" wrong.' % (ca_name))
166
ca = opensslcnf.getcadata(ca_name)
168
# Eine Benutzerantwort ist eingetroffen
169
request_filename = os.path.join(ca.pend_reqs_dir,'%s.%s.%s' % (prefix,ca_name,caChallengeId))
171
pubkey_filename = '%s.%s' % (request_filename,request_filenamesuffix)
173
# Existieren die benoetigten Dateien?
174
if not os.path.isfile(pubkey_filename):
175
LogWrite(logfile,'Error',m,'Certificate request file %s not found.' % (pubkey_filename))
178
# Hier sind jetzt alle Angaben gueltig, soweit pruefbar
180
newrequest_filename = os.path.join(ca.new_reqs_dir,'%s.%s.%s' % (prefix,ca_name,caChallengeId))
181
target_pubkey_filename = '%s.%s' % (newrequest_filename,request_filenamesuffix)
183
# Now copy files to target directory, use copy to get new ownership
185
shutil.copyfile(pubkey_filename,target_pubkey_filename)
186
os.chmod(target_pubkey_filename,0440)
188
LogWrite(logfile,'Error',m,'Copying %s to %s failed.' % (pubkey_filename,target_pubkey_filename))
192
os.remove(pubkey_filename)
194
LogWrite(logfile,'Error',m,'Removing %s failed.' % (pubkey_filename))
197
LogWrite(logfile,'Challenge',m,'Request challenge: %s Id=%s' % (ca_name,caChallengeId))
199
# FIX ME! We also would like to look into PKCS10 requests!
200
if prefix!='cert-req-SPKAC':
203
# Read the certificate request file
204
certreq = openssl.cert.SPKACClass(target_pubkey_filename)
206
certreq_name_attr = certreq.data.get('commonName','')
207
certreq_mail_attr = certreq.data.get('emailAddress','')
209
if (certreq_name_attr and from_name!=certreq_name_attr) or \
210
(certreq_mail_attr and from_mail!=certreq_mail_attr):
212
cacert = openssl.cert.X509CertificateClass(ca.certificate)
213
ca_from_addr = cacert.subject.get('Email',pyca_section.get('caAdminMailAdr',''))
214
mail_msg = """From: %s <%s>
216
Subject: Your confirmation e-mail with ID %s
218
We received the correct confirmation e-mail for your
221
However the from: field of your confirmation e-mail
225
did not match the attributes
230
given in your certificate request. Your certificate request will
231
be processed anyway. But if you intend to use the requested
232
certificate for signing e-mails you might want to adjust the from
233
address in your mail clients preferences / options menu to avoid
234
trouble with other mail users reporting invalid signatures.
236
If you have further questions simply reply to this e-mail.
240
cacert.subject.get('CN','CA administrator'))
246
certreq_name_attr,certreq_mail_attr
250
smtpconn=smtplib.SMTP(MailRelay)
253
smtpconn.set_debuglevel(0)
254
smtpconn.sendmail(ca_from_addr,certreq_mail_attr,mail_msg)
258
LogWrite(logfile,'Error',m,'Unable to send an e-mail to %s!\n' % (certreq_mail_attr))
260
LogWrite(logfile,'Error',m,'Sent from address warning to %s!\n' % (certreq_mail_attr))
262
LogWrite(logfile,'Error',m,'Unable to contact default mail relay %s!\n' % (MailRelay))