Open and close files manually. It prevents from leaking files, preliminary creation of output files, and accidental closing of stdin and stdout.
140 lines
4.0 KiB
Python
Executable File
140 lines
4.0 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
|
|
"""
|
|
This script should be called *manually* when we want to upgrade SSLError
|
|
`library` and `reason` mnemonics to a more recent OpenSSL version.
|
|
|
|
It takes two arguments:
|
|
- the path to the OpenSSL source tree (e.g. git checkout)
|
|
- the path to the header file to be generated Modules/_ssl_data_{version}.h
|
|
- error codes are version specific
|
|
"""
|
|
|
|
import argparse
|
|
import datetime
|
|
import operator
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate ssl_data.h from OpenSSL sources"
|
|
)
|
|
parser.add_argument("srcdir", help="OpenSSL source directory")
|
|
parser.add_argument(
|
|
"output", nargs="?", default=None
|
|
)
|
|
|
|
|
|
def _file_search(fname, pat):
|
|
with open(fname, encoding="utf-8") as f:
|
|
for line in f:
|
|
match = pat.search(line)
|
|
if match is not None:
|
|
yield match
|
|
|
|
|
|
def parse_err_h(args):
|
|
"""Parse err codes, e.g. ERR_LIB_X509: 11"""
|
|
pat = re.compile(r"#\s*define\W+ERR_LIB_(\w+)\s+(\d+)")
|
|
lib2errnum = {}
|
|
for match in _file_search(args.err_h, pat):
|
|
libname, num = match.groups()
|
|
lib2errnum[libname] = int(num)
|
|
|
|
return lib2errnum
|
|
|
|
|
|
def parse_openssl_error_text(args):
|
|
"""Parse error reasons, X509_R_AKID_MISMATCH"""
|
|
# ignore backslash line continuation for now
|
|
pat = re.compile(r"^((\w+?)_R_(\w+)):(\d+):")
|
|
for match in _file_search(args.errtxt, pat):
|
|
reason, libname, errname, num = match.groups()
|
|
if "_F_" in reason:
|
|
# ignore function codes
|
|
continue
|
|
num = int(num)
|
|
yield reason, libname, errname, num
|
|
|
|
|
|
def parse_extra_reasons(args):
|
|
"""Parse extra reasons from openssl.ec"""
|
|
pat = re.compile(r"^R\s+((\w+)_R_(\w+))\s+(\d+)")
|
|
for match in _file_search(args.errcodes, pat):
|
|
reason, libname, errname, num = match.groups()
|
|
num = int(num)
|
|
yield reason, libname, errname, num
|
|
|
|
|
|
def gen_library_codes(args):
|
|
"""Generate table short libname to numeric code"""
|
|
yield "static struct py_ssl_library_code library_codes[] = {"
|
|
for libname in sorted(args.lib2errnum):
|
|
yield f"#ifdef ERR_LIB_{libname}"
|
|
yield f' {{"{libname}", ERR_LIB_{libname}}},'
|
|
yield "#endif"
|
|
yield " { NULL }"
|
|
yield "};"
|
|
yield ""
|
|
|
|
|
|
def gen_error_codes(args):
|
|
"""Generate error code table for error reasons"""
|
|
yield "static struct py_ssl_error_code error_codes[] = {"
|
|
for reason, libname, errname, num in args.reasons:
|
|
yield f" #ifdef {reason}"
|
|
yield f' {{"{errname}", ERR_LIB_{libname}, {reason}}},'
|
|
yield " #else"
|
|
yield f' {{"{errname}", {args.lib2errnum[libname]}, {num}}},'
|
|
yield " #endif"
|
|
|
|
yield " { NULL }"
|
|
yield "};"
|
|
yield ""
|
|
|
|
|
|
def main():
|
|
args = parser.parse_args()
|
|
|
|
args.err_h = os.path.join(args.srcdir, "include", "openssl", "err.h")
|
|
if not os.path.isfile(args.err_h):
|
|
# Fall back to infile for OpenSSL 3.0.0
|
|
args.err_h += ".in"
|
|
args.errcodes = os.path.join(args.srcdir, "crypto", "err", "openssl.ec")
|
|
args.errtxt = os.path.join(args.srcdir, "crypto", "err", "openssl.txt")
|
|
|
|
if not os.path.isfile(args.errtxt):
|
|
parser.error(f"File {args.errtxt} not found in srcdir\n.")
|
|
|
|
# {X509: 11, ...}
|
|
args.lib2errnum = parse_err_h(args)
|
|
|
|
# [('X509_R_AKID_MISMATCH', 'X509', 'AKID_MISMATCH', 110), ...]
|
|
reasons = []
|
|
reasons.extend(parse_openssl_error_text(args))
|
|
reasons.extend(parse_extra_reasons(args))
|
|
# sort by libname, numeric error code
|
|
args.reasons = sorted(reasons, key=operator.itemgetter(0, 3))
|
|
|
|
lines = [
|
|
"/* File generated by Tools/ssl/make_ssl_data.py */"
|
|
f"/* Generated on {datetime.datetime.utcnow().isoformat()} */"
|
|
]
|
|
lines.extend(gen_library_codes(args))
|
|
lines.append("")
|
|
lines.extend(gen_error_codes(args))
|
|
|
|
if args.output is None:
|
|
for line in lines:
|
|
print(line)
|
|
else:
|
|
with open(args.output, 'w') as output:
|
|
for line in lines:
|
|
print(line, file=output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|