169 lines
5.2 KiB
PHP
169 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace Orbit;
|
|
|
|
use Monolog\Logger;
|
|
use Monolog\Handler\StreamHandler;
|
|
|
|
class Server
|
|
{
|
|
public static $version = "0.2";
|
|
|
|
public $config;
|
|
public $tls_certfile = "./server.cert.pem";
|
|
public $tls_keyfile = "./server.key.pem";
|
|
public $key_passphrase = "";
|
|
public $timeout = 60;
|
|
|
|
private $ssl_context;
|
|
private $logger;
|
|
|
|
public function __construct(Config $config = null, Logger $logger = null)
|
|
{
|
|
if ($config == null) {
|
|
$this->config = new Config();
|
|
} else {
|
|
$this->config = $config;
|
|
}
|
|
|
|
if ($logger !== null) {
|
|
$this->setLogger($logger);
|
|
}
|
|
|
|
$this->timeout = ini_get("default_socket_timeout");
|
|
|
|
if (file_exists($this->config->tls_certfile) && file_exists($this->config->tls_keyfile)) {
|
|
$this->log(Logger::DEBUG, "Using existing cert + key.");
|
|
} else {
|
|
$this->log(Logger::DEBUG, "Generating new cert.");
|
|
if (file_exists($this->config->tls_certfile)) {
|
|
$this->log(Logger::WARNING, "Warning! May overwrite existing cert file '" . $this->config->tls_certfile . "'\n");
|
|
}
|
|
if (file_exists($this->config->tls_keyfile)) {
|
|
$this->log(Logger::WARNING, "Warning! May overwrite existing key file '" . $this->config->tls_keyfile . "'\n");
|
|
}
|
|
$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::INFO, "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;
|
|
}
|
|
$error = sprintf("Error %s: %s", $id, $message);
|
|
$this->log(Logger::ERROR, "$client_name Closed");
|
|
|
|
// Something else happened.
|
|
throw new \Exception($error);
|
|
}
|
|
|
|
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" => $this->config->hostname,
|
|
"emailAddress" => "X"
|
|
];
|
|
|
|
// Generate certificate
|
|
$privkey = openssl_pkey_new();
|
|
$cert = openssl_csr_new($dn, $privkey);
|
|
$cert = openssl_csr_sign($cert, null, $privkey, 365);
|
|
|
|
// Generate PEM files
|
|
$pem = [];
|
|
openssl_x509_export($cert, $pem[0]);
|
|
openssl_pkey_export($privkey, $pem[1], $this->config->key_passphrase);
|
|
|
|
// Save PEM files
|
|
file_put_contents($this->config->tls_certfile, $pem[0]);
|
|
file_put_contents($this->config->tls_keyfile, $pem[1]);
|
|
}
|
|
|
|
public function createSslContext()
|
|
{
|
|
$context = stream_context_create();
|
|
|
|
// local_cert must be in PEM format
|
|
stream_context_set_option($context, 'ssl', 'local_cert', $this->config->tls_certfile);
|
|
stream_context_set_option($context, 'ssl', 'local_pk', $this->config->tls_keyfile);
|
|
stream_context_set_option($context, 'ssl', 'passphrase', $this->config->key_passphrase);
|
|
|
|
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
|
|
stream_context_set_option($context, 'ssl', 'verify_peer', false);
|
|
|
|
return $context;
|
|
}
|
|
}
|