Make the preverify_ok value accessible
This commit is contained in:
parent
4570cabcaf
commit
b5acfb594b
75
jetforce.py
75
jetforce.py
|
@ -320,21 +320,22 @@ class StaticDirectoryApplication(JetforceApplication):
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_arguments(cls, parser: argparse.ArgumentParser):
|
def add_arguments(cls, parser: argparse.ArgumentParser):
|
||||||
# fmt: off
|
# fmt: off
|
||||||
parser.add_argument(
|
group = parser.add_argument_group("static file configuration")
|
||||||
|
group.add_argument(
|
||||||
"--dir",
|
"--dir",
|
||||||
help="Root directory on the filesystem to serve",
|
help="Root directory on the filesystem to serve",
|
||||||
default="/var/gemini",
|
default="/var/gemini",
|
||||||
metavar="DIR",
|
metavar="DIR",
|
||||||
dest="root_directory",
|
dest="root_directory",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--cgi-dir",
|
"--cgi-dir",
|
||||||
help="CGI script directory, relative to the server's root directory",
|
help="CGI script directory, relative to the server's root directory",
|
||||||
default="cgi-bin",
|
default="cgi-bin",
|
||||||
metavar="DIR",
|
metavar="DIR",
|
||||||
dest="cgi_directory",
|
dest="cgi_directory",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--index-file",
|
"--index-file",
|
||||||
help="If a directory contains a file with this name, "
|
help="If a directory contains a file with this name, "
|
||||||
"that file will be served instead of auto-generating an index page",
|
"that file will be served instead of auto-generating an index page",
|
||||||
|
@ -400,7 +401,8 @@ class StaticDirectoryApplication(JetforceApplication):
|
||||||
stream to the client.
|
stream to the client.
|
||||||
"""
|
"""
|
||||||
script_name = str(filesystem_path)
|
script_name = str(filesystem_path)
|
||||||
cgi_env = environ.copy()
|
|
||||||
|
cgi_env = {k: v for k, v in environ.items() if k.isupper()}
|
||||||
cgi_env["GATEWAY_INTERFACE"] = "GCI/1.1"
|
cgi_env["GATEWAY_INTERFACE"] = "GCI/1.1"
|
||||||
cgi_env["SCRIPT_NAME"] = script_name
|
cgi_env["SCRIPT_NAME"] = script_name
|
||||||
|
|
||||||
|
@ -510,7 +512,6 @@ class GeminiProtocol(LineOnlyReceiver):
|
||||||
TIMESTAMP_FORMAT = "%d/%b/%Y:%H:%M:%S %z"
|
TIMESTAMP_FORMAT = "%d/%b/%Y:%H:%M:%S %z"
|
||||||
|
|
||||||
client_addr: typing.Union[IPv4Address, IPv6Address]
|
client_addr: typing.Union[IPv4Address, IPv6Address]
|
||||||
client_cert: typing.Optional[x509.Certificate]
|
|
||||||
connected_timestamp: time.struct_time
|
connected_timestamp: time.struct_time
|
||||||
request: bytes
|
request: bytes
|
||||||
url: str
|
url: str
|
||||||
|
@ -531,11 +532,6 @@ class GeminiProtocol(LineOnlyReceiver):
|
||||||
self.response_size = 0
|
self.response_size = 0
|
||||||
self.response_buffer = ""
|
self.response_buffer = ""
|
||||||
self.client_addr = self.transport.getPeer()
|
self.client_addr = self.transport.getPeer()
|
||||||
self.client_cert = None
|
|
||||||
|
|
||||||
peer_cert = self.transport.getPeerCertificate()
|
|
||||||
if peer_cert:
|
|
||||||
self.client_cert = peer_cert.to_cryptography()
|
|
||||||
|
|
||||||
def lineReceived(self, line):
|
def lineReceived(self, line):
|
||||||
"""
|
"""
|
||||||
|
@ -576,7 +572,8 @@ class GeminiProtocol(LineOnlyReceiver):
|
||||||
"""
|
"""
|
||||||
Construct a dictionary that will be passed to the application handler.
|
Construct a dictionary that will be passed to the application handler.
|
||||||
|
|
||||||
Variable names conform to the CGI spec defined in RFC 3875.
|
Variable names (mostly) conform to the CGI spec defined in RFC 3875.
|
||||||
|
The TLS variable names borrow from the GLV-1.12556 server.
|
||||||
"""
|
"""
|
||||||
url_parts = urllib.parse.urlparse(self.url)
|
url_parts = urllib.parse.urlparse(self.url)
|
||||||
environ = {
|
environ = {
|
||||||
|
@ -590,21 +587,39 @@ class GeminiProtocol(LineOnlyReceiver):
|
||||||
"SERVER_PORT": str(self.client_addr.port),
|
"SERVER_PORT": str(self.client_addr.port),
|
||||||
"SERVER_PROTOCOL": "GEMINI",
|
"SERVER_PROTOCOL": "GEMINI",
|
||||||
"SERVER_SOFTWARE": f"jetforce/{__version__}",
|
"SERVER_SOFTWARE": f"jetforce/{__version__}",
|
||||||
|
"client_certificate": None,
|
||||||
}
|
}
|
||||||
if self.client_cert:
|
|
||||||
# Extract useful information from the client certificate. These
|
peer_certificate = self.transport.getPeerCertificate()
|
||||||
# mostly follow the naming convention from GLV-1.12556
|
if peer_certificate:
|
||||||
cert = self.client_cert
|
cert = peer_certificate.to_cryptography()
|
||||||
|
environ["client_certificate"] = cert
|
||||||
|
|
||||||
|
# Extract useful information from the client certificate.
|
||||||
name_attrs = cert.subject.get_attributes_for_oid(CN)
|
name_attrs = cert.subject.get_attributes_for_oid(CN)
|
||||||
common_name = name_attrs[0].value if name_attrs else ""
|
common_name = name_attrs[0].value if name_attrs else ""
|
||||||
|
|
||||||
fingerprint_bytes = cert.fingerprint(hashes.SHA256())
|
fingerprint_bytes = cert.fingerprint(hashes.SHA256())
|
||||||
fingerprint = base64.b64encode(fingerprint_bytes).decode()
|
fingerprint = base64.b64encode(fingerprint_bytes).decode()
|
||||||
|
|
||||||
not_before = cert.not_valid_before.strftime("%Y-%m-%dT%H:%M:%SZ")
|
not_before = cert.not_valid_before.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
not_after = cert.not_valid_after.strftime("%Y-%m-%dT%H:%M:%SZ")
|
not_after = cert.not_valid_after.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
conn = self.transport.getHandle()
|
||||||
|
|
||||||
|
tls_version = conn.get_protocol_version_name()
|
||||||
|
tls_cipher = conn.get_cipher_name()
|
||||||
|
|
||||||
|
# Grab the value that we stashed during the TLS handshake.
|
||||||
|
verified = getattr(conn, "preverify_ok", False)
|
||||||
|
|
||||||
environ.update(
|
environ.update(
|
||||||
{
|
{
|
||||||
"AUTH_TYPE": "CERTIFICATE",
|
"AUTH_TYPE": "CERTIFICATE",
|
||||||
"REMOTE_USER": common_name,
|
"REMOTE_USER": common_name,
|
||||||
|
"TLS_CIPHER": tls_cipher,
|
||||||
|
"TLS_VERSION": tls_version,
|
||||||
|
"TLS_CLIENT_VERIFIED": verified,
|
||||||
"TLS_CLIENT_HASH": fingerprint,
|
"TLS_CLIENT_HASH": fingerprint,
|
||||||
"TLS_CLIENT_NOT_BEFORE": not_before,
|
"TLS_CLIENT_NOT_BEFORE": not_before,
|
||||||
"TLS_CLIENT_NOT_AFTER": not_after,
|
"TLS_CLIENT_NOT_AFTER": not_after,
|
||||||
|
@ -746,7 +761,8 @@ class GeminiOpenSSLCertificateOptions(CertificateOptions):
|
||||||
library. Returning true will always allow client certificates, even if
|
library. Returning true will always allow client certificates, even if
|
||||||
they are self-signed.
|
they are self-signed.
|
||||||
"""
|
"""
|
||||||
return preverify_ok
|
conn.preverify_ok = preverify_ok
|
||||||
|
return True
|
||||||
|
|
||||||
def proto_select_callback(self, conn, protocols):
|
def proto_select_callback(self, conn, protocols):
|
||||||
"""
|
"""
|
||||||
|
@ -905,7 +921,7 @@ class GeminiServer(Factory):
|
||||||
raiseMinimumTo=TLSVersion.TLSv1_3,
|
raiseMinimumTo=TLSVersion.TLSv1_3,
|
||||||
requireCertificate=False,
|
requireCertificate=False,
|
||||||
fixBrokenPeers=True,
|
fixBrokenPeers=True,
|
||||||
# ALPN, I may look into supporting this later
|
# This is for ALPN, I may look into supporting this later.
|
||||||
acceptableProtocols=None,
|
acceptableProtocols=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -938,41 +954,42 @@ class GeminiServer(Factory):
|
||||||
action="version",
|
action="version",
|
||||||
version="jetforce " + __version__
|
version="jetforce " + __version__
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group = parser.add_argument_group("server configuration")
|
||||||
|
group.add_argument(
|
||||||
"--host",
|
"--host",
|
||||||
help="Server address to bind to",
|
help="Server address to bind to",
|
||||||
default="127.0.0.1"
|
default="127.0.0.1"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--port",
|
"--port",
|
||||||
help="Server port to bind to",
|
help="Server port to bind to",
|
||||||
type=int,
|
type=int,
|
||||||
default=1965
|
default=1965
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--hostname",
|
"--hostname",
|
||||||
help="Server hostname",
|
help="Server hostname",
|
||||||
default="localhost"
|
default="localhost"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--tls-certfile",
|
"--tls-certfile",
|
||||||
dest="certfile",
|
dest="certfile",
|
||||||
help="Server TLS certificate file",
|
help="Server TLS certificate file",
|
||||||
metavar="FILE",
|
metavar="FILE",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--tls-keyfile",
|
"--tls-keyfile",
|
||||||
dest="keyfile",
|
dest="keyfile",
|
||||||
help="Server TLS private key file",
|
help="Server TLS private key file",
|
||||||
metavar="FILE",
|
metavar="FILE",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--tls-cafile",
|
"--tls-cafile",
|
||||||
dest="cafile",
|
dest="cafile",
|
||||||
help="A CA file to use for validating clients",
|
help="A CA file to use for validating clients",
|
||||||
metavar="FILE",
|
metavar="FILE",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
group.add_argument(
|
||||||
"--tls-capath",
|
"--tls-capath",
|
||||||
dest="capath",
|
dest="capath",
|
||||||
help="A directory containing CA files for validating clients",
|
help="A directory containing CA files for validating clients",
|
||||||
|
@ -982,14 +999,12 @@ class GeminiServer(Factory):
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_argparse(
|
def from_command_line(
|
||||||
cls, app_class: typing.Type[JetforceApplication], reactor: ReactorBase = reactor
|
cls, app_class: typing.Type[JetforceApplication], reactor: ReactorBase = reactor
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Shortcut to parse command line arguments and build a server instance.
|
Shortcut to parse command line arguments and build a server instance
|
||||||
|
for a class-based jetforce application.
|
||||||
This only works with class-based Jetforce applications that subclass
|
|
||||||
from JetforceApplication.
|
|
||||||
"""
|
"""
|
||||||
parser = cls.build_argument_parser()
|
parser = cls.build_argument_parser()
|
||||||
app_class.add_arguments(parser)
|
app_class.add_arguments(parser)
|
||||||
|
@ -1010,7 +1025,7 @@ def run_server() -> None:
|
||||||
"""
|
"""
|
||||||
Entry point for running the static directory server.
|
Entry point for running the static directory server.
|
||||||
"""
|
"""
|
||||||
server = GeminiServer.from_argparse(app_class=StaticDirectoryApplication)
|
server = GeminiServer.from_command_line(app_class=StaticDirectoryApplication)
|
||||||
server.run()
|
server.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -17,7 +17,7 @@ setuptools.setup(
|
||||||
author_email="lazar.michael22@gmail.com",
|
author_email="lazar.michael22@gmail.com",
|
||||||
description="An Experimental Gemini Server",
|
description="An Experimental Gemini Server",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"twisted",
|
"twisted>=20.3.0",
|
||||||
"service_identity", # Used by twisted
|
"service_identity", # Used by twisted
|
||||||
"idna", # Used by twisted
|
"idna", # Used by twisted
|
||||||
"pyopenssl", # Used by twisted
|
"pyopenssl", # Used by twisted
|
||||||
|
|
Loading…
Reference in New Issue