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