#!/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-mails 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= \ --revision= --release-versions= \ --release-date= ... 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 \ ... """ 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-mails 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()