#!/usr/bin/python import argparse import os import pwd import subprocess import sys import tempfile import urllib2 import warnings from jinja2 import Template def get_full_name(): full_name = os.getenv('DEBFULLNAME') if full_name: return full_name.decode('utf-8') return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0].decode('utf-8') try: import rdflib except ImportError: warnings.warn('python-rdflib not installed; will fall back to PTS email address') def get_maintainers(pkg): return u'{}@packages.debian.org'.format(pkg) else: def get_maintainers(pkg): import re, urllib # RDF object and predicate references used on PTS project = rdflib.term.URIRef(u'http://packages.qa.debian.org/{}#project'.format(pkg)) has_contributor = rdflib.term.URIRef(u'http://schema.org/contributor') is_named = rdflib.term.URIRef(u'http://xmlns.com/foaf/0.1/name') is_same_as = rdflib.term.URIRef(u'http://www.w3.org/2002/07/owl#sameAs') maint = [] graph = rdflib.Graph() try: graph.parse('https://packages.qa.debian.org/{}/{}.rdf' .format(re.match('((?:lib)?.)', pkg).group(1), pkg)) except urllib2.HTTPError, exc: if exc.code == 404: raise ValueError("unknown package '{}'".format(pkg)) raise for contrib in graph[project : has_contributor]: names = [n for n in graph[contrib : is_named]] addresses = [urllib.unquote(m.group(1)) for m in map(re.compile(r'http://webid\.debian\.net/maintainers/(.*)#agent$').match, graph[contrib : is_same_as]) if m] if not names or not addresses: warnings.warn('found contributor missing name and/or address') continue address = addresses[0] if '@' not in address: address += '@debian.org' maint.append(u'"{}" <{}>'.format(names[0], address)) return u', '.join(maint) # Parse command line parser = argparse.ArgumentParser( description='Get in touch with package maintainers') parser.add_argument('--force', action='store_true', help='Ignore safety checks') parser.add_argument('--lts', action='store_true', help='Act as a member of the LTS team') parser.add_argument('--no-dsa', dest='no_dsa', action='store_true', help='Say that issues are low severity (no need for DSA/DLA)') parser.add_argument('--mailer', action='store', default='mutt -H {}', help='Command executed. Must contain {} to be replaced ' 'by the filename of the draft contact mail') parser.add_argument('package') parser.add_argument('cve', nargs='*') args = parser.parse_args() cc = 'debian-lts@lists.debian.org' if args.lts else 'team@security.debian.org' team = 'lts' if args.lts else 'sec' model = 'no-dsa' if args.no_dsa else 'update-planned' template_file = 'templates/{}-{}.txt'.format(team, model) # Basic check instructions = "packages/{}.txt".format(args.package) if os.path.exists(instructions) and not args.force: print("Have a look at {}".format(instructions)) print("If you still want to run this script, run it with --force.") sys.exit(1) # Check if we should contact maintainers dontcall = "data/packages/lts-do-not-call" if args.lts and not args.force: with open(dontcall) as f: for line in f: if line[0] == '#': continue if not line.strip(): continue if line.split()[0] == args.package: print "Maintainer(s) may not be contacted for LTS issues." print("Please have a look at {}".format(line.split()[1])) print("If you still want to run this script, run it with --force.") sys.exit(1) # Generate the context # XXX: Once that 761859 is fixed, improve the logic here to: # - retrieve the current list of CVE dynamically # - check whether we should use the no-dsa variant of the template # - check whether we have an open bug report, in which case we should # include it in the recipients of the mail context = { 'package': args.package, 'sender': get_full_name(), 'cve': args.cve, 'to': get_maintainers(args.package), 'cc': cc, 'uploaders': '' } # Generate the mail with open(template_file) as f: template = Template(f.read().decode('utf-8')) fd, filename = tempfile.mkstemp(prefix='contact-maintainers', suffix='.txt') draft = os.fdopen(fd, 'w') draft.write(template.render(context).encode('utf-8')) draft.close() os.system(args.mailer.format(filename)) os.unlink(filename)