=> 3f372e730e881135dfa7d5677499b1c908a36b90
[1mdiff --git a/gmcapsule/__init__.py b/gmcapsule/__init__.py[m [1mindex 27e3a2f..010c8df 100644[m [1m--- a/gmcapsule/__init__.py[m [1m+++ b/gmcapsule/__init__.py[m [36m@@ -85,6 +85,10 @@[m [mserver[m host : string [string...][m One or more hostnames for the server. Defaults to ``localhost``.[m [m [32m+[m[32m When multiple hostnames are specified, each becomes a virtual host.[m [32m+[m[32m The certs directory (see below) may contain separate certificate[m [32m+[m[32m files for each virtual host.[m [32m+[m address : string[m IP address of the network interface where the server is listening.[m Defaults to ``0.0.0.0`` (all interfaces).[m [36m@@ -93,10 +97,16 @@[m [mport : int[m IP port on which the server is listening.[m [m certs : path[m [31m- Directory where the server certificate is stored. The directory must[m [32m+[m[32m Directory where server certificates are stored. The directory must[m contain the PEM-formatted files `cert.pem` and `key.pem`. Defaults[m to `.certs`.[m [m [32m+[m[32m If virtual hosts are in use (multiple hostnames configured), this[m [32m+[m[32m directory can have subdirectories matching each hostname. These[m [32m+[m[32m host-specific subdirectories are also expected to contain the[m [32m+[m[32m PEM-formatted files `cert.pem` and `key.pem`. As a fallback, the[m [32m+[m[32m common certificate at the top level of the certs directory is used.[m [32m+[m modules : path [path...][m One or more directories to load extension modules from.[m [m [36m@@ -506,7 +516,7 @@[m [mfrom .gemini import Server, Cache, Context, Identity, GeminiError[m from .markdown import to_gemtext as markdown_to_gemtext[m [m [m [31m-__version__ = '0.9.0'[m [32m+[m[32m__version__ = '0.9.1'[m __all__ = [[m 'Config', 'Cache', 'Context', 'GeminiError', 'Identity',[m 'get_mime_type', 'markdown_to_gemtext'[m [1mdiff --git a/gmcapsule/gemini.py b/gmcapsule/gemini.py[m [1mindex c5cea90..c70f2f0 100644[m [1m--- a/gmcapsule/gemini.py[m [1m+++ b/gmcapsule/gemini.py[m [36m@@ -867,27 +867,57 @@[m [mclass Server:[m def __init__(self, cfg):[m mp.set_start_method('spawn')[m [m [31m- self.cfg = cfg[m [32m+[m[32m self.cfg = cfg[m self.address = cfg.address()[m [31m- self.port = cfg.port()[m [31m-[m [31m- cert_path = cfg.certs_dir() / 'cert.pem'[m [31m- key_path = cfg.certs_dir() / 'key.pem'[m [32m+[m[32m self.port = cfg.port()[m [m [31m- if not os.path.exists(cert_path):[m [31m- raise Exception("certificate file not found: " + str(cert_path))[m [31m- if not os.path.exists(key_path):[m [31m- raise Exception("private key file not found: " + str(key_path))[m [32m+[m[32m self.main_context = None[m [32m+[m[32m self.contexts = {}[m [32m+[m[32m session_id = f'GmCapsule:{cfg.port()}'.encode('utf-8')[m [m [31m- self.context = SSL.Context(SSL.TLS_SERVER_METHOD)[m [31m- self.context.use_certificate_file(str(cert_path))[m [31m- self.context.use_privatekey_file(str(key_path))[m [31m- self.context.set_verify(SSL.VERIFY_PEER, verify_callback)[m [32m+[m[32m for host in cfg.hostnames():[m [32m+[m[32m ctx = SSL.Context(SSL.TLS_SERVER_METHOD)[m [32m+[m[32m ctx.set_verify(SSL.VERIFY_PEER, verify_callback)[m [32m+[m[32m ctx.set_session_id(session_id)[m [32m+[m[32m self.contexts[host] = ctx[m [32m+[m [32m+[m[32m if not self.main_context:[m [32m+[m[32m self.main_context = ctx[m [32m+[m [32m+[m[32m keys_found = False[m [32m+[m [32m+[m[32m # Try the domain-specific certificates first.[m [32m+[m[32m cert_path = cfg.certs_dir() / host / 'cert.pem'[m [32m+[m[32m key_path = cfg.certs_dir() / host / 'key.pem'[m [32m+[m[32m if cert_path.exists() and key_path.exists():[m [32m+[m[32m print(f'Host "{host}": Using certificate {cert_path}')[m [32m+[m[32m ctx.use_certificate_file(str(cert_path))[m [32m+[m[32m ctx.use_privatekey_file(str(key_path))[m [32m+[m[32m keys_found = True[m [32m+[m [32m+[m[32m if not keys_found:[m [32m+[m[32m cert_path = cfg.certs_dir() / 'cert.pem'[m [32m+[m[32m key_path = cfg.certs_dir() / 'key.pem'[m [32m+[m[32m if os.path.exists(cert_path) and os.path.exists(key_path):[m [32m+[m[32m print(f'Host "{host}": Using default certificate {cert_path}')[m [32m+[m[32m ctx.use_certificate_file(str(cert_path))[m [32m+[m[32m ctx.use_privatekey_file(str(key_path))[m [32m+[m[32m keys_found = True[m [32m+[m [32m+[m[32m if not keys_found:[m [32m+[m[32m raise Exception(f"certificate not found for host '{host}'; check {str(cfg.certs_dir())}")[m [32m+[m [32m+[m[32m def _select_ssl_context(conn):[m [32m+[m[32m name = conn.get_servername().decode('utf-8')[m [32m+[m[32m ctx = self.main_context[m [32m+[m[32m if name in self.contexts:[m [32m+[m[32m print('selecting context', self.contexts[name])[m [32m+[m[32m ctx = self.contexts[name][m [32m+[m[32m else:[m [32m+[m[32m print('selecting MAIN context', self.main_context)[m [32m+[m[32m conn.set_context(ctx)[m [m [31m- session_id = f'GmCapsule:{cfg.port()}'.encode('utf-8')[m [31m- #if type(session_id) != bytes:[m [31m- # raise Exception("session_id type must be `bytes`")[m [31m- self.context.set_session_id(session_id)[m [32m+[m[32m self.main_context.set_tlsext_servername_callback(_select_ssl_context)[m [m # Spawn the worker threads.[m self.parser_queue = queue.Queue()[m [36m@@ -909,7 +939,7 @@[m [mclass Server:[m self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)[m self.sock.bind((self.address, self.port))[m self.sock.listen(5)[m [31m- self.sv_conn = SSL.Connection(self.context, self.sock)[m [32m+[m[32m self.sv_conn = SSL.Connection(self.main_context, self.sock)[m self.sv_conn.set_accept_state()[m break[m except:[m
text/gemini; charset=utf-8
This content has been proxied by September (3851b).