blob: 00b79b8c6287fa906e835e4b38f9f61f45ded4c2 [file] [log] [blame]
#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
"""
Send GPG-signed security advisory e-mayails from an @apache.org address
to a known list of recipients, or write the advisory text in a
form suitable for publishing on http://subversion.apache.org/.
Usage: cd to the root directory of the advisory descriptions, then:
$ ${TRUNK_WC}/tools/dist/advisory.py send \
--username=<ASF-username> \
--revision=<dist-dev-revision-number>
--release-versions=<target-releases> \
--release-date=<expected-release-date> <CVE-number>...
or
$ ${TRUNK_WC}/tools/dist/advisory.py test \
(... --username, etc. as above)
or
$ ${TRUNK_WC}/tools/dist/advisory.py generate \
--destination=${SITE_WC}/publish/security \
<CVE-number>...
"""
from __future__ import absolute_import
import os
import sys
import argparse
import datetime
import getpass
import re
import security.parser
import security.adviser
import security.mailer
import security.mailinglist
ROOTDIR = os.path.abspath(os.getcwd())
NOTICE_TEMPLATE = 'notice-template.txt'
MAILING_LIST = 'pre-notifications.txt'
def parse_args(argv):
parser = argparse.ArgumentParser(
prog=os.path.basename(__file__), add_help=True,
description="""\
Send GPG-signed security advisory e-mayails from an @apache.org address
to a known list of recipients, or write the advisory text in a
form suitable for publishing on http://subversion.apache.org/.
""")
parser.add_argument(
'command', action='store',
choices=['send', 'test', 'generate'],
help=('send: send mail; '
'test: write the mail to standard output; '
'generate: write an advisory for the website'))
parser.add_argument(
'--username', action='store', required=False,
help='the @apache.org username of the sender')
parser.add_argument(
'--revision', action='store', required=False, type=int,
help=('revision on dist.a.o./repos/dist/dev/subversion '
'in which the patched tarballs are available'))
parser.add_argument(
'--release-versions', action='store', required=False,
help=('comma-separated list of future released versions '
'that will contain the fix(es)'))
parser.add_argument(
'--release-date', action='store', required=False,
help=('expected release date for the above mentioned'
' versions (in ISO format, YYYY-MM-DD)'))
parser.add_argument(
'--destination', action='store', required=False,
help=('the directory where the website advisory should be '
'written; usually ${SITE_WC}/publish/security'))
parser.add_argument('cve', nargs='+')
return parser.parse_args(argv)
def check_root():
if not os.path.isfile(os.path.join(ROOTDIR, NOTICE_TEMPLATE)):
sys.stderr.write('Missing file: ' + NOTICE_TEMPLATE + '\n')
sys.exit(1)
if not os.path.isfile(os.path.join(ROOTDIR, MAILING_LIST)):
sys.stderr.write('Missing file: ' + MAILING_LIST + '\n')
sys.exit(1)
def check_sendmail(args):
if (not (args.username and args.revision
and args.release_versions
and args.release_date and args.cve)
or args.destination):
sys.stderr.write(
'The "' + args.command + '" command requires the '
'following options:\n'
' --username, --revision, --release-versions, --release-date\n'
' and a list of CVE numbers.\n')
sys.exit(1)
args.release_versions = re.split(r'\s*,\s*', args.release_versions)
args.release_date = datetime.datetime.strptime(args.release_date,
'%Y-%m-%d')
def sendmail(really_send, args):
notice_template = os.path.join(ROOTDIR, NOTICE_TEMPLATE)
mailing_list = os.path.join(ROOTDIR, MAILING_LIST)
sender = args.username + '@apache.org'
notification = security.parser.Notification(ROOTDIR, *args.cve)
mailer = security.mailer.Mailer(notification,
args.username + '@apache.org',
notice_template,
args.release_date,
args.revision,
*args.release_versions)
message = mailer.generate_message()
recipients = security.mailinglist.MailingList(mailing_list)
if (not really_send):
sys.stdout.write(message.as_string())
return
password = getpass.getpass('Password for ' + args.username
+ ' at mail-relay.apache.org: ')
mailer.send_mail(message, args.username, password,
recipients=recipients)
def check_generate(args):
if (not (args.destination and args.cve)
or args.username or args.revision
or args.release_versions
or args.release_date):
sys.stderr.write(
'The "generate" command requires the '
'--destination option '
'and a list of CVE numbers.\n')
sys.exit(1)
if not os.path.isdir(args.destination):
sys.stderr.write(args.destination + ' is not a directory')
sys.exit(1)
def generate(args):
notification = security.parser.Notification(ROOTDIR, *args.cve)
security.adviser.generate(notification, args.destination);
def main():
check_root()
args = parse_args(sys.argv[1:])
if args.command in ('send', 'test'):
check_sendmail(args)
sendmail(args.command == 'send', args)
elif args.command == 'generate':
check_generate(args)
generate(args)
if __name__ == '__main__':
main()