|
|
9ed232 |
#!/usr/bin/python
|
|
|
9ed232 |
#
|
|
|
9ed232 |
# CGI script to handle file updates for the rpms CVS repository. There
|
|
|
9ed232 |
# is nothing really complex here other than tedious checking of our
|
|
|
9ed232 |
# every step along the way...
|
|
|
9ed232 |
#
|
|
|
9ed232 |
# Written for Fedora, modified to suit CentOS Infrastructure.
|
|
|
9ed232 |
# Modified by Howard Johnson <merlin@merlinthp.org> 2014
|
|
|
9ed232 |
#
|
|
|
9ed232 |
# License: GPL
|
|
|
9ed232 |
|
|
|
9ed232 |
#
|
|
|
9ed232 |
# centos' lookaside is a bit differently laid out to fedora's.
|
|
|
9ed232 |
# centos uses a <package>/<branch>/<sha1sum> scheme.
|
|
|
9ed232 |
#
|
|
|
9ed232 |
# The upload.cgi gets called with the following arguments:
|
|
|
9ed232 |
# name - package (git repo) name
|
|
|
9ed232 |
# branch - branch name
|
|
|
9ed232 |
# sha1sum - SHA1 checksum of the file
|
|
|
9ed232 |
# file - the file to upload (optional)
|
|
|
9ed232 |
#
|
|
|
9ed232 |
# With only the first three args, the script runs in check mode.
|
|
|
9ed232 |
# With the fourth too, it operates in upload mode.
|
|
|
9ed232 |
#
|
|
|
9ed232 |
|
|
|
9ed232 |
import os
|
|
|
9ed232 |
import sys
|
|
|
9ed232 |
import cgi
|
|
|
9ed232 |
import tempfile
|
|
|
9ed232 |
import syslog
|
|
|
9ed232 |
import smtplib
|
|
|
9ed232 |
import re
|
|
|
9ed232 |
from ConfigParser import SafeConfigParser
|
|
|
9ed232 |
|
|
|
9ed232 |
from email import Header, Utils
|
|
|
9ed232 |
try:
|
|
|
9ed232 |
from email.mime.text import MIMEText
|
|
|
9ed232 |
except ImportError:
|
|
|
9ed232 |
from email.MIMEText import MIMEText
|
|
|
9ed232 |
|
|
|
9ed232 |
import hashlib
|
|
|
9ed232 |
sha1_constructor = hashlib.sha1
|
|
|
9ed232 |
|
|
|
9ed232 |
# Config file with all our settings
|
|
|
9ed232 |
CONFIG = '/etc/lookaside.cfg'
|
|
|
9ed232 |
conf = SafeConfigParser()
|
|
|
9ed232 |
conf.read(CONFIG)
|
|
|
9ed232 |
|
|
|
9ed232 |
# Reading buffer size
|
|
|
9ed232 |
BUFFER_SIZE = 4096
|
|
|
9ed232 |
|
|
|
9ed232 |
# Gitblit config file regexes
|
|
|
9ed232 |
SECTION = re.compile(r'\[(.+)\]')
|
|
|
9ed232 |
OPTION = re.compile(r'(.+)=(.+)')
|
|
|
9ed232 |
REPO = re.compile(r'([^/]+).git$')
|
|
|
9ed232 |
|
|
|
9ed232 |
def merge_gitblit_section(repoacl, repos, users):
|
|
|
9ed232 |
for repo in repos:
|
|
|
9ed232 |
if repo not in repoacl:
|
|
|
9ed232 |
repoacl[repo] = []
|
|
|
9ed232 |
for user in users:
|
|
|
9ed232 |
if user not in repoacl[repo]:
|
|
|
9ed232 |
repoacl[repo].append(user)
|
|
|
9ed232 |
|
|
|
9ed232 |
# turns the gitblit config file into a dict of package name to permitted users
|
|
|
9ed232 |
def parse_gitblit_config(filename):
|
|
|
9ed232 |
insection = False
|
|
|
9ed232 |
repoacl = {}
|
|
|
9ed232 |
sectrepos = []
|
|
|
9ed232 |
sectusers = []
|
|
|
9ed232 |
|
|
|
9ed232 |
f = open(filename)
|
|
|
9ed232 |
for line in f:
|
|
|
9ed232 |
if line.strip() == '' or line[0] == '#':
|
|
|
9ed232 |
continue
|
|
|
9ed232 |
secth = SECTION.match(line)
|
|
|
9ed232 |
if secth:
|
|
|
9ed232 |
if insection:
|
|
|
9ed232 |
merge_gitblit_section(repoacl, sectrepos, sectusers)
|
|
|
9ed232 |
|
|
|
9ed232 |
sectrepos = []
|
|
|
9ed232 |
sectusers = []
|
|
|
9ed232 |
else:
|
|
|
9ed232 |
insection = True
|
|
|
9ed232 |
continue
|
|
|
9ed232 |
opt = OPTION.match(line)
|
|
|
9ed232 |
if opt:
|
|
|
9ed232 |
if not insection:
|
|
|
9ed232 |
continue
|
|
|
9ed232 |
key = opt.group(1).strip()
|
|
|
9ed232 |
value = opt.group(2).strip()
|
|
|
9ed232 |
|
|
|
9ed232 |
if key == "repository":
|
|
|
9ed232 |
pack = REPO.search(value)
|
|
|
9ed232 |
if pack:
|
|
|
9ed232 |
sectrepos.append(pack.group(1))
|
|
|
9ed232 |
elif key == "user":
|
|
|
9ed232 |
sectusers.append(value)
|
|
|
9ed232 |
if insection:
|
|
|
9ed232 |
merge_gitblit_section(repoacl, sectrepos, sectusers)
|
|
|
9ed232 |
f.close()
|
|
|
9ed232 |
return repoacl
|
|
|
9ed232 |
|
|
|
9ed232 |
def send_error(text):
|
|
|
9ed232 |
print text
|
|
|
9ed232 |
sys.exit(1)
|
|
|
9ed232 |
|
|
|
9ed232 |
def check_form(form, var):
|
|
|
9ed232 |
ret = form.getvalue(var, None)
|
|
|
9ed232 |
if ret is None:
|
|
|
9ed232 |
send_error('Required field "%s" is not present.' % var)
|
|
|
9ed232 |
if isinstance(ret, list):
|
|
|
9ed232 |
send_error('Multiple values given for "%s". Aborting.' % var)
|
|
|
9ed232 |
return ret
|
|
|
9ed232 |
|
|
|
9ed232 |
def send_email(pkg, sha1, filename, username):
|
|
|
9ed232 |
text = """A file has been added to the lookaside cache for %(pkg)s:
|
|
|
9ed232 |
|
|
|
9ed232 |
%(sha1)s %(filename)s""" % locals()
|
|
|
9ed232 |
msg = MIMEText(text)
|
|
|
9ed232 |
sender_name = conf.get('mail', 'sender_name')
|
|
|
9ed232 |
sender_email = conf.get('mail', 'sender_email')
|
|
|
9ed232 |
sender = Utils.formataddr((sender_name, sender_email))
|
|
|
9ed232 |
recipient = conf.get('mail', 'recipient')
|
|
|
9ed232 |
msg['Subject'] = 'File %s uploaded to lookaside cache by %s' % (
|
|
|
9ed232 |
filename, username)
|
|
|
9ed232 |
msg['From'] = sender
|
|
|
9ed232 |
msg['To'] = recipient
|
|
|
9ed232 |
try:
|
|
|
9ed232 |
s = smtplib.SMTP(conf.get('mail', 'smtp_server'))
|
|
|
9ed232 |
s.sendmail(sender, recipient, msg.as_string())
|
|
|
9ed232 |
except:
|
|
|
9ed232 |
errstr = 'sending mail for upload of %s failed!' % filename
|
|
|
9ed232 |
print >> sys.stderr, errstr
|
|
|
9ed232 |
syslog.syslog(errstr)
|
|
|
9ed232 |
|
|
|
9ed232 |
def main():
|
|
|
9ed232 |
os.umask(002)
|
|
|
9ed232 |
|
|
|
9ed232 |
username = os.environ.get('SSL_CLIENT_S_DN_CN', None)
|
|
|
9ed232 |
|
|
|
9ed232 |
print 'Content-Type: text/plain'
|
|
|
9ed232 |
print
|
|
|
9ed232 |
|
|
|
9ed232 |
assert os.environ['REQUEST_URI'].split('/')[1] == 'lookaside'
|
|
|
9ed232 |
|
|
|
9ed232 |
form = cgi.FieldStorage()
|
|
|
9ed232 |
name = check_form(form, 'name')
|
|
|
9ed232 |
branch = check_form(form, 'branch')
|
|
|
9ed232 |
sha1sum = check_form(form, 'sha1sum')
|
|
|
9ed232 |
|
|
|
9ed232 |
action = None
|
|
|
9ed232 |
upload_file = None
|
|
|
9ed232 |
filename = None
|
|
|
9ed232 |
|
|
|
9ed232 |
# Is this a submission or a test?
|
|
|
9ed232 |
# in a test, we don't get a file.
|
|
|
9ed232 |
if not form.has_key('file'):
|
|
|
9ed232 |
action = 'check'
|
|
|
9ed232 |
print >> sys.stderr, '[username=%s] Checking file status: NAME=%s BRANCH=%s SHA1SUM=%s' % (username, name, branch, sha1sum)
|
|
|
9ed232 |
else:
|
|
|
9ed232 |
action = 'upload'
|
|
|
9ed232 |
upload_file = form['file']
|
|
|
9ed232 |
if not upload_file.file:
|
|
|
9ed232 |
send_error('No file given for upload. Aborting.')
|
|
|
9ed232 |
filename = os.path.basename(upload_file.filename)
|
|
|
9ed232 |
print >> sys.stderr, '[username=%s] Processing upload request: NAME=%s BRANCH=%s SHA1SUM=%s' % (username, name, branch, sha1sum)
|
|
|
9ed232 |
|
|
|
9ed232 |
module_dir = os.path.join(conf.get('lookaside', 'cache_dir'), name, branch)
|
|
|
9ed232 |
dest_file = os.path.join(module_dir, sha1sum)
|
|
|
9ed232 |
|
|
|
9ed232 |
# try to see if we already have this file...
|
|
|
9ed232 |
if os.path.exists(dest_file):
|
|
|
9ed232 |
if action == 'check':
|
|
|
9ed232 |
print 'Available'
|
|
|
9ed232 |
else:
|
|
|
9ed232 |
upload_file.file.close()
|
|
|
9ed232 |
dest_file_stat = os.stat(dest_file)
|
|
|
9ed232 |
print 'File %s already exists' % filename
|
|
|
9ed232 |
print 'File: %s Size: %d' % (dest_file, dest_file_stat.st_size)
|
|
|
9ed232 |
sys.exit(0)
|
|
|
9ed232 |
elif action == 'check':
|
|
|
9ed232 |
print 'Missing'
|
|
|
9ed232 |
sys.exit(0)
|
|
|
9ed232 |
|
|
|
9ed232 |
# if desired, make sure the user has permission to write to this branch
|
|
|
9ed232 |
if conf.getboolean('acl', 'do_acl'):
|
|
|
9ed232 |
# load in the gitblit config
|
|
|
9ed232 |
repoacl = parse_gitblit_config(conf.get('acl', 'gitblit_config'))
|
|
|
9ed232 |
|
|
|
9ed232 |
# if the package isn't in the gitblit config, we can't give upload perms
|
|
|
9ed232 |
if name not in repoacl:
|
|
|
9ed232 |
send_error("Unknown package %s" % name)
|
|
|
9ed232 |
|
|
|
9ed232 |
# now check the perms
|
|
|
9ed232 |
if username not in repoacl[name]:
|
|
|
9ed232 |
send_error("Write access package %s rejected for user %s" % (name, username))
|
|
|
9ed232 |
|
|
|
9ed232 |
# check that all directories are in place
|
|
|
9ed232 |
if not os.path.isdir(module_dir):
|
|
|
9ed232 |
os.makedirs(module_dir, 02775)
|
|
|
9ed232 |
|
|
|
9ed232 |
# grab a temporary filename and dump our file in there
|
|
|
9ed232 |
tempfile.tempdir = module_dir
|
|
|
9ed232 |
tmpfile = tempfile.mkstemp(sha1sum)[1]
|
|
|
9ed232 |
tmpfd = open(tmpfile, 'w')
|
|
|
9ed232 |
|
|
|
9ed232 |
# now read the whole file in
|
|
|
9ed232 |
m = sha1_constructor()
|
|
|
9ed232 |
filesize = 0
|
|
|
9ed232 |
while True:
|
|
|
9ed232 |
data = upload_file.file.read(BUFFER_SIZE)
|
|
|
9ed232 |
if not data:
|
|
|
9ed232 |
break
|
|
|
9ed232 |
tmpfd.write(data)
|
|
|
9ed232 |
m.update(data)
|
|
|
9ed232 |
filesize += len(data)
|
|
|
9ed232 |
|
|
|
9ed232 |
# now we're done reading, check the MD5 sum of what we got
|
|
|
9ed232 |
tmpfd.close()
|
|
|
9ed232 |
check_sha1sum = m.hexdigest()
|
|
|
9ed232 |
if sha1sum != check_sha1sum:
|
|
|
9ed232 |
os.unlink(tmpfile)
|
|
|
9ed232 |
send_error("SHA1 check failed. Received %s instead of %s." % (check_sha1sum, sha1sum))
|
|
|
9ed232 |
|
|
|
9ed232 |
# rename it its final name
|
|
|
9ed232 |
os.rename(tmpfile, dest_file)
|
|
|
9ed232 |
os.chmod(dest_file, 0644)
|
|
|
9ed232 |
|
|
|
9ed232 |
print >> sys.stderr, '[username=%s] Stored %s (%d bytes)' % (username, dest_file, filesize)
|
|
|
9ed232 |
print 'File %s size %d SHA1 %s stored OK' % (filename, filesize, sha1sum)
|
|
|
9ed232 |
if conf.getboolean('mail', 'send_mail'):
|
|
|
9ed232 |
send_email(name, sha1sum, filename, username)
|
|
|
9ed232 |
|
|
|
9ed232 |
if __name__ == '__main__':
|
|
|
9ed232 |
main()
|