| #!/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() |