config = new Config(); } else { $this->config = $config; } $this->timeout = ini_get("default_socket_timeout"); if (file_exists($this->pemfile)) { $this->log(Logger::DEBUG, "Using existing cert."); } else { $this->log(Logger::DEBUG, "Generating new cert."); $this->generateCert(); } $this->ssl_context = $this->createSslContext(); } public function setLogger(Logger $logger) { $this->logger = $logger; } public function getLogger() { if (!$this->logger) { $this->logger = new Logger('orbit'); $this->logger->pushHandler(new StreamHandler($this->config->log_file, Logger::INFO)); } return $this->logger; } public function listen() { $server = stream_socket_server( $this->getListenAddress(), $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $this->ssl_context ); if (!$server) { throw new \Exception("Error " . $errno . ": " . $errstr); } $protocol = "gemini"; $name = stream_socket_get_name($server, false); $this->log(Logger::NOTICE, "Listening on $protocol://$name ..."); while (true) { # This is to swallow up the `timeout` warning set_error_handler([$this, 'onWarning']); $client = stream_socket_accept($server, $this->timeout, $client_name); restore_error_handler(); if ($client) { $this->log(Logger::DEBUG, "$client_name Accepted"); $request_buffer = stream_get_line($client, 1024, "\r\n"); $this->log(Logger::INFO, "REQ: $request_buffer", ["client" => $client_name]); $request = new Request($request_buffer); // Respond to client $response = implode("\r\n", ['20 text/gemini', "Thanks for all the fish\n"]); fwrite($client, $response); fclose($client); $this->log(Logger::DEBUG, "$client_name Closed"); } } } public function log($level, $message, $context = []) { $this->getLogger()->log($level, $message, $context); } public function onWarning($id, $message) { if (strpos($message, "Operation timed out") !== false) { // Do nothing return; } // Something else happened. throw new \Exception($id . ' ' . $message); } public function getListenAddress() { return sprintf('tls://%s:%s', $this->config->host, $this->config->port); } private function generateCert() { // Certificate data $dn = [ "countryName" => "UK", "stateOrProvinceName" => "X", "localityName" => "X", "organizationName" => "X", "organizationalUnitName" => "X", "commonName" => "orbit1.0", "emailAddress" => "X" ]; // Generate certificate $privkey = openssl_pkey_new(); $cert = openssl_csr_new($dn, $privkey); $cert = openssl_csr_sign($cert, null, $privkey, 365); // Generate PEM file $pem = []; openssl_x509_export($cert, $pem[0]); openssl_pkey_export($privkey, $pem[1], $this->pem_passphrase); $pem = implode($pem); // Save PEM file file_put_contents($this->pemfile, $pem); } public function createSslContext() { $context = stream_context_create(); // local_cert must be in PEM format stream_context_set_option($context, 'ssl', 'local_cert', $this->pemfile); stream_context_set_option($context, 'ssl', 'passphrase', $this->pem_passphrase); stream_context_set_option($context, 'ssl', 'allow_self_signed', true); stream_context_set_option($context, 'ssl', 'verify_peer', false); return $context; } }