summaryrefslogtreecommitdiffstats
path: root/bin/contact-maintainers
blob: 12d19d6dc1f9ca6ca2d04c83bc56efadca7ebd8f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env python3

import argparse
import os
import pwd
import re
import sys
import tempfile
import urllib
import warnings

from jinja2 import Template


def get_full_name():
    full_name = os.getenv("DEBFULLNAME")
    if full_name:
        return full_name
    return pwd.getpwuid(os.getuid()).pw_gecos.split(",")[0]


def get_maintainers(pkg):
    return u"{}@packages.debian.org".format(pkg)


try:
    import rdflib
except ImportError:
    warnings.warn("python-rdflib not installed; will fall back to PTS email address")
else:

    def get_maintainers(pkg):

        # 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 urllib.error.HTTPError as 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.parse.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(
    "--minor",
    dest="minor_issues",
    action="store_true",
    help="Say that issues are low severity and someone will work on them (LTS team only)",
)
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"
minor = "-minor" if args.minor_issues and args.lts else ""
template_file = "templates/{}-{}{}.txt".format(team, model, minor)

# 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("Reason: {}".format(" ".join(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())

fd, filename = tempfile.mkstemp(prefix="contact-maintainers", suffix=".txt")
draft = os.fdopen(fd, "wb")
draft.write(template.render(context).encode("utf-8"))
draft.close()

os.system(args.mailer.format(filename))
os.unlink(filename)

© 2014-2024 Faster IT GmbH | imprint | privacy policy