Blame lookaside/upload.cgi

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()