import sys, os
import sqlite3
def make_config():
'Output a blank config file'
if os.path.exists('config.py'):
print "'config.py' already exists!'"
sys.exit(1)
conf = open('config.py', 'w')
conf.write("version = ''\n")
conf.write("sigdir = ''\n")
conf.write("filesdir = ''\n")
conf.close()
print "'config.py' generated"
def make_db():
'Initialize a blank database'
db = sqlite3.connect('sigs.db')
db.execute('''
CREATE TABLE signatures (
keyid TEXT, filename TEXT, signature BLOB,
UNIQUE(keyid,filename)
);
''');
def generate_asc_files(target_dir='.'):
fds = {}
def _open(filename):
if not fds.has_key(filename):
fd = open(os.path.join(target_dir, filename + '.asc'), 'w')
fds[filename] = fd
return fds[filename]
db = sqlite3.connect(os.path.join(target_dir, 'sigs.db'))
curs = db.cursor()
curs.execute('SELECT filename, signature FROM signatures;')
for filename, signature in curs:
fd = _open(filename)
fd.write(signature + "\n")
for fd in fds.values():
fd.flush()
fd.close()
actions = {
'make_config' : make_config,
'make_db' : make_db,
'make_asc' : generate_asc_files,
}
if __name__ == '__main__':
if len(sys.argv) > 1:
if sys.argv[1] in actions:
actions[sys.argv[1]]()
sys.exit(0)
import cgi
import cgitb
cgitb.enable()
import string, subprocess, re
try:
sys.path.append(os.path.dirname(sys.argv[0]))
import config
except:
print 'Content-type: text/plain'
print
print 'Cannot find config file'
sys.exit(1)
r = re.compile('^\[GNUPG\:\] GOODSIG (\w*) (.*)')
def files():
for f in os.listdir(config.filesdir):
if config.version in f and (f.endswith('.tar.gz') or f.endswith('.zip') or f.endswith('.tar.bz2')):
yield f
def ordinal(N):
try:
return [None, 'first', 'second', 'third', 'fourth', 'fifth', 'sixth'][N]
except:
return "%dth" % N
shell_content = '''
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Signature collection for Subversion $version</title>
</head>
<body style="font-size: 14pt; text-align: justify;
background-color: #f0f0f0; padding: 0 5%%">
<p>This page is used to collect <a href="%s/list">signatures</a> for the
proposed release of Apache Subversion $version.</p>
$content
</body>
</html>
''' % os.getenv('SCRIPT_NAME')
signature_area = '''
<hr/>
<form method="post" action="%s">
<p>Paste one or more signatures in the area below:<br/>
<textarea name="signatures" rows="20" cols="80"></textarea>
</p>
<input type="submit" value="Submit" />
<p>Any text not between the <tt>BEGIN PGP SIGNATURE</tt>
and <tt>END PGP SIGNATURE</tt> lines will be ignored.</p>
</form>
<hr/>
''' % os.getenv('SCRIPT_NAME')
def split(sigs):
lines = []
for line in sigs.split('\n'):
if lines or '--BEGIN' in line:
lines.append(line)
if '--END' in line:
yield "\n".join(lines) + "\n"
lines = []
def list_signatures():
db = sqlite3.connect(os.path.join(config.sigdir, 'sigs.db'))
template = '''
<hr/>
<p>The following signature files are available:</p>
<p>%s</p>
'''
lines = ""
curs = db.cursor()
curs.execute('''SELECT filename, COUNT(*) FROM signatures
GROUP BY filename ORDER BY filename''')
for filename, count in curs:
lines += '<a href="%s/%s.asc">%s.asc</a>: %d signature%s<br/>\n' \
% (os.getenv('SCRIPT_NAME'), filename, filename,
count, ['s', ''][count == 1])
return (template % lines) + signature_area
def save_valid_sig(db, filename, keyid, signature):
db.execute('INSERT OR REPLACE INTO signatures VALUES (?,?,?);',
(keyid, filename, buffer(signature)))
db.commit()
generate_asc_files(config.sigdir)
def verify_sig_for_file(signature, filename):
args = ['gpg', '--logger-fd', '1', '--no-tty',
'--status-fd', '2', '--verify', '-',
os.path.join(config.filesdir, filename)]
gpg = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
gpg.stdin.write(signature)
gpg.stdin.close()
rc = gpg.wait()
output = gpg.stdout.read()
status = gpg.stderr.read()
if rc:
return (False, status + output)
lines = status.split('\n')
for line in lines:
match = r.search(line)
if match:
keyid = match.group(1)
user = match.group(2)
return (True, (filename, keyid, user))
def verify_sig(signature):
all_failures = ""
for filename in files():
(verified, result) = verify_sig_for_file(signature, filename)
if verified:
return (verified, result)
else:
all_failures += "%s:\n[[[\n%s]]]\n\n" % (filename, result)
return (False, all_failures)
def process_sigs(signatures):
success = '''
<p style="color: green;">All %d signatures verified!</p>
'''
failure = '''
<p style="color: red;">%d of %d signatures failed to verify; details below.</p>
'''
c_verified = '''
<p style="color: green;">The signature is verified!</p>
<p>Filename: <code>%s</code></p>
<p>Key ID: <code>%s</code></p>
<p>User: <code>%s</code></p>
<p>This signature has been saved, and will be included as part of the
release signatures.</p>
'''
c_unverified = '''
<p style="color: red;">The signature was not able to be verified!</p>
<p>Signature: <pre>%s</pre></p>
<p>Reason:</p><pre>%s</pre>
<p>Please talk to the release manager if this is in error.</p>
'''
outcomes = []
N_sigs = 0
N_verified = 0
retval = ''
db = sqlite3.connect(os.path.join(config.sigdir, 'sigs.db'))
for signature in split(signatures):
N_sigs += 1
(verified, result) = verify_sig(signature)
outcomes.append((verified, result))
if verified:
(filename, keyid, user) = result
save_valid_sig(db, filename, keyid, signature)
N_verified += 1
if N_verified == N_sigs:
retval += success % N_sigs
else:
retval += failure % (N_sigs-N_verified, N_sigs)
N = 0
for outcome in outcomes:
N += 1
(verified, result) = outcome
retval += "<h1>Results for the %s signature</h1>" % ordinal(N)
if verified:
(filename, keyid, user) = result
retval += c_verified % (filename, keyid[-8:], user)
else:
retval += c_unverified % (signature, result)
return retval + signature_area
def cat_signatures(basename):
assert basename[:-4] in files()
ascfile = os.path.join(config.sigdir, basename)
if os.path.exists(ascfile):
return (open(ascfile, 'r').read())
def print_content_type(mimetype):
print "Content-Type: " + mimetype
print
def main():
form = cgi.FieldStorage()
pathinfo = os.getenv('PATH_INFO')
content = signature_area
if 'signatures' in form:
content = process_sigs(form['signatures'].value)
elif pathinfo and pathinfo[1:]:
basename = pathinfo.split('/')[-1]
if basename == 'list':
content = list_signatures()
elif basename[:-4] in files():
print_content_type('text/plain')
print cat_signatures(basename)
return
mapping = {
'version' : config.version,
'content' : content,
}
print_content_type('text/html')
template = string.Template(shell_content)
print template.safe_substitute(mapping)
if __name__ == '__main__':
main()