mirror of
https://github.com/eapl-gemugami/gemini-php.git
synced 2025-02-08 04:54:18 +00:00
Fixes to allow symbolic links, and formatting fixes
This commit is contained in:
parent
d237016c0c
commit
ad7e3c5ead
71
README.md
Normal file
71
README.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Gemini-PHP
|
||||||
|
Gemini-PHP is a Gemini server written in PHP by @neil@glasgow.social.
|
||||||
|
It's designed more for teaching than practical use. That's said - it's very simple to get up and running and we're hosting this page on it - it seems to be performing well.
|
||||||
|
If you have any questions or want to get in touch, you can join our community on Matrix at #gemini-php:glasgow.social
|
||||||
|
|
||||||
|
## How to install
|
||||||
|
* Download via git
|
||||||
|
```
|
||||||
|
git clone https://coding.openguide.co.uk/git/gemini-php/
|
||||||
|
```
|
||||||
|
|
||||||
|
* Enter the project directory and create a certificate for your server (a self signed certificate is fine, in fact it's encouraged!)
|
||||||
|
```
|
||||||
|
cd gemini-php
|
||||||
|
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
|
||||||
|
```
|
||||||
|
* Combine your private key with the certificate and put in the certs directory
|
||||||
|
|
||||||
|
```
|
||||||
|
cp cert.pem certs/combined.pem
|
||||||
|
cat key.pem >> certs/combined.pem
|
||||||
|
```
|
||||||
|
* Create a config file from the sample
|
||||||
|
```
|
||||||
|
cp config.php.sample config.php
|
||||||
|
```
|
||||||
|
* Then edit it with the location of your new certificate - most other options are optional
|
||||||
|
* Start your server with
|
||||||
|
```
|
||||||
|
php server.php
|
||||||
|
```
|
||||||
|
* You should be able to visit your new server in any Gemini client (remember to open your firewall if needed - post 1965)
|
||||||
|
|
||||||
|
## Using Gemini-PHP
|
||||||
|
* The basic index file is located in hosts/default/index.gemini - edit this to get started
|
||||||
|
* Gemini-PHP supports multiple virtual hosts, just create a directory with the name of the domain you expect to receive requests for, i.e.
|
||||||
|
```
|
||||||
|
mkdir hosts/glasgow.social
|
||||||
|
mkdir hosts/projects.glasgow.social
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running as a service
|
||||||
|
To set up the server as a service, create the following file in /etc/systemd/system/gemini-php.service
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=Gemini-PHP Service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=gemini
|
||||||
|
Type=simple
|
||||||
|
TimeoutSec=0
|
||||||
|
WorkingDirectory=/home/gemini/gemini-php/
|
||||||
|
PIDFile=/var/run/gemini-php.pid
|
||||||
|
ExecStart=/usr/bin/php -f /home/gemini/gemini-php/server.php
|
||||||
|
KillMode=process
|
||||||
|
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=42s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
```
|
||||||
|
Note, customise the above to the user you are running gemini-php as (we recommend creating a new user account for this to keep it relatively isolated) as well as the path to the script.
|
||||||
|
Enable the script with systemctl
|
||||||
|
```
|
||||||
|
sudo systemctl enable gemini-php
|
||||||
|
sudo systemctl start gemini-php
|
||||||
|
systemctl status gemini-php
|
||||||
|
|
||||||
|
sudo systemctl stop gemini-php
|
||||||
|
```
|
@ -5,7 +5,7 @@
|
|||||||
*
|
*
|
||||||
* This is your certificate file. A self-signed certificate is acceptable here.
|
* This is your certificate file. A self-signed certificate is acceptable here.
|
||||||
* You can generate one using:
|
* You can generate one using:
|
||||||
*
|
*
|
||||||
* openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
|
* openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
|
||||||
*
|
*
|
||||||
* Then combine the key and certificate and copy them to the certs directory:
|
* Then combine the key and certificate and copy them to the certs directory:
|
||||||
@ -34,10 +34,8 @@ $config['certificate_passphrase'] = '';
|
|||||||
|
|
||||||
// Default index file. If a path isn't specified then the server will
|
// Default index file. If a path isn't specified then the server will
|
||||||
// default to an index file (like index.html on a web server).
|
// default to an index file (like index.html on a web server).
|
||||||
//$config['default_index_file'] = "index.gemini";
|
//$config['default_index_file'] = "index.gmi";
|
||||||
|
|
||||||
// Logging, setting this to false will disable logging (default is on/true);
|
// Logging, setting this to false will disable logging (default is on/true);
|
||||||
//$config['logging'] = true;
|
//$config['logging'] = true;
|
||||||
//$config['log_file'] = "logs/gemini-php.log";
|
//$config['log_file'] = "logs/gemini-php.log";
|
||||||
|
|
||||||
?>
|
|
||||||
|
129
gemini.class.php
129
gemini.class.php
@ -3,67 +3,92 @@ error_reporting(E_ALL);
|
|||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
ob_implicit_flush();
|
ob_implicit_flush();
|
||||||
|
|
||||||
class Gemini {
|
// Based on:
|
||||||
|
// gemini://glasgow.social/gemini-php
|
||||||
|
|
||||||
|
class Gemini {
|
||||||
function __construct($config) {
|
function __construct($config) {
|
||||||
if(empty($config['certificate_file']))
|
if (empty($config['certificate_file'])) {
|
||||||
die("Missing certificate file. Edit config.php\n");
|
die('Missing certificate file. Edit config.php\n');
|
||||||
$this->ip = "0";
|
}
|
||||||
$this->port = "1965";
|
|
||||||
$this->data_dir = "hosts/";
|
$this->ip = '0';
|
||||||
$this->default_host_dir = "default/";
|
$this->port = '1965';
|
||||||
$this->default_index_file = "index.gemini";
|
$this->data_dir = 'hosts/';
|
||||||
|
$this->default_host_dir = 'default/';
|
||||||
|
$this->default_index_file = 'index.gmi';
|
||||||
$this->logging = 1;
|
$this->logging = 1;
|
||||||
$this->log_file = "logs/gemini-php.log";
|
$this->log_file = "logs/gemini-php.log";
|
||||||
$this->log_sep = "\t";
|
$this->log_sep = "\t";
|
||||||
$settings = array('ip', 'port', 'data_dir', 'default_host_dir', 'default_index_file',
|
|
||||||
'certificate_file', 'certificate_passphrase');
|
$settings = array(
|
||||||
foreach($settings as $setting_key) {
|
'ip', 'port',
|
||||||
if(!empty($config[$setting_key]))
|
'data_dir',
|
||||||
|
'default_host_dir',
|
||||||
|
'default_index_file',
|
||||||
|
'certificate_file',
|
||||||
|
'certificate_passphrase'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($settings as $setting_key) {
|
||||||
|
if (!empty($config[$setting_key])) {
|
||||||
$this->$setting_key = $config[$setting_key];
|
$this->$setting_key = $config[$setting_key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// append the required filepath slashes if they're missing
|
|
||||||
if(substr($this->data_dir, -1) != "/")
|
// Append the required filepath slashes if they're missing
|
||||||
|
if (substr($this->data_dir, -1) != "/") {
|
||||||
$this->data_dir .= "/";
|
$this->data_dir .= "/";
|
||||||
if(substr($this->default_host_dir, -1) != "/")
|
}
|
||||||
|
if (substr($this->default_host_dir, -1) != "/") {
|
||||||
$this->default_host_dir .= "/";
|
$this->default_host_dir .= "/";
|
||||||
if($this->logging) {
|
}
|
||||||
if(!file_exists($this->log_file)) {
|
if ($this->logging) {
|
||||||
|
if (!file_exists($this->log_file)) {
|
||||||
$this->log_to_file("Log created", null, null, null, null);
|
$this->log_to_file("Log created", null, null, null, null);
|
||||||
}
|
}
|
||||||
if(!is_writable($this->log_file)) {
|
if (!is_writable($this->log_file)) {
|
||||||
die("{$this->log_file} is not writable.\n");
|
die("{$this->log_file} is not writable.\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!is_readable($this->certificate_file))
|
if (!is_readable($this->certificate_file)) {
|
||||||
die("Certificate file {$this->certificate_file} not readable.\n");
|
die("Certificate file {$this->certificate_file} not readable.\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_request($request) {
|
function parse_request($request) {
|
||||||
$url = trim($request); // strip <CR><LF> from the end
|
$url = trim($request); // Strip <CR><LF> from the end
|
||||||
return parse_url($url);
|
return parse_url($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_valid_hosts() {
|
function get_valid_hosts() {
|
||||||
$dirs = array_map('basename', glob($this->data_dir.'*', GLOB_ONLYDIR));
|
$dirs = array_map('basename', glob($this->data_dir . '*', GLOB_ONLYDIR));
|
||||||
return $dirs;
|
return $dirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_status_code($filepath) {
|
function get_status_code($filepath) {
|
||||||
if(is_file($filepath) and file_exists($filepath))
|
if (is_file($filepath) and file_exists($filepath)) {
|
||||||
return "20";
|
return '20';
|
||||||
if(!file_exists($filepath))
|
}
|
||||||
return "51";
|
if (!file_exists($filepath)) {
|
||||||
return "50";
|
//echo("File $filepath doesn't exist\n");
|
||||||
|
return '51';
|
||||||
|
}
|
||||||
|
return '50';
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_mime_type($filepath) {
|
function get_mime_type($filepath) {
|
||||||
$type = mime_content_type($filepath);
|
$type = mime_content_type($filepath);
|
||||||
// we need a way to detect gemini file types, which PHP doesn't
|
// We need a way to detect gemini file types, which PHP doesn't
|
||||||
// so.. if it ends with gemini (or if it has no extension), assume
|
// so.. if it ends with gemini (or if it has no extension), assume
|
||||||
$path_parts = pathinfo($filepath);
|
$path_parts = pathinfo($filepath);
|
||||||
if(empty($path_parts['extension']) or $path_parts['extension'] == "gemini")
|
|
||||||
$type = "text/gemini";
|
if (empty($path_parts['extension'])
|
||||||
|
or $path_parts['extension'] === 'gemini'
|
||||||
|
or $path_parts['extension'] === 'gmi'
|
||||||
|
) {
|
||||||
|
$type = 'text/gemini';
|
||||||
|
}
|
||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,31 +104,42 @@ class Gemini {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function get_filepath($url) {
|
function get_filepath($url) {
|
||||||
$hostname = "";
|
$hostname = '';
|
||||||
if(!is_array($url))
|
|
||||||
|
if (!is_array($url)) {
|
||||||
return false;
|
return false;
|
||||||
if(!empty($url['host']))
|
}
|
||||||
|
if (!empty($url['host'])) {
|
||||||
$hostname = $url['host'];
|
$hostname = $url['host'];
|
||||||
|
}
|
||||||
|
|
||||||
$valid_hosts = $this->get_valid_hosts();
|
$valid_hosts = $this->get_valid_hosts();
|
||||||
if(!in_array($hostname, $valid_hosts))
|
|
||||||
$hostname = "default";
|
if (!in_array($hostname, $valid_hosts)) {
|
||||||
|
$hostname = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
// Kristall Browser is adding "__" to the end of the filenames
|
// Kristall Browser is adding "__" to the end of the filenames
|
||||||
// wtf am I missing?
|
// wtf am I missing?
|
||||||
// also removing ".." to mitigate against directory traversal
|
// also removing ".." to mitigate against directory traversal
|
||||||
$url['path'] = str_replace(array("..", "__"), "", $url['path']);
|
$url['path'] = str_replace(array('..', '__'), '', $url['path']);
|
||||||
// force an index file to be appended if a filename is missing
|
|
||||||
if(empty($url['path'])) {
|
// Force an index file to be appended if a filename is missing
|
||||||
$url['path'] = "/".$this->default_index_file;
|
if (empty($url['path'])) {
|
||||||
} elseif(substr($url['path'], -1) == "/") {
|
$url['path'] = '/' . $this->default_index_file;
|
||||||
|
} elseif(substr($url['path'], -1) === '/') {
|
||||||
$url['path'] .= $this->default_index_file;
|
$url['path'] .= $this->default_index_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
$valid_data_dir = dirname(__FILE__)."/".$this->data_dir;
|
$valid_data_dir = dirname(__FILE__) . '/' . $this->data_dir;
|
||||||
$return_path = $this->data_dir.$hostname.$url['path'];
|
$return_path = $this->data_dir.$hostname.$url['path'];
|
||||||
// check the real path is in the data_dir (path traversal sanity check)
|
|
||||||
if(substr(realpath($return_path),0, strlen($valid_data_dir)) == $valid_data_dir) {
|
if (is_link($return_path)) {
|
||||||
|
return $return_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the real path is in the data_dir (path traversal sanity check)
|
||||||
|
if (substr(realpath($return_path), 0, strlen($valid_data_dir)) === $valid_data_dir) {
|
||||||
return $return_path;
|
return $return_path;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -112,10 +148,11 @@ class Gemini {
|
|||||||
function log_to_file($ip, $status_code, $meta, $filepath, $filesize) {
|
function log_to_file($ip, $status_code, $meta, $filepath, $filesize) {
|
||||||
$ts = date("Y-m-d H:i:s", strtotime('now'));
|
$ts = date("Y-m-d H:i:s", strtotime('now'));
|
||||||
$this->log_sep;
|
$this->log_sep;
|
||||||
$str = $ts.$this->log_sep.$ip.$this->log_sep.$status_code.$this->log_sep.
|
|
||||||
$meta.$this->log_sep.$filepath.$this->log_sep.$filesize."\n";
|
$str = $ts.$this->log_sep . $ip . $this->log_sep
|
||||||
|
. $status_code . $this->log_sep
|
||||||
|
. $meta.$this->log_sep . $filepath . $this->log_sep
|
||||||
|
. $filesize . "\n";
|
||||||
file_put_contents($this->log_file, $str, FILE_APPEND);
|
file_put_contents($this->log_file, $str, FILE_APPEND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
# Success!
|
|
||||||
Your Gemini server is up and running!
|
|
||||||
|
|
||||||
You can read more about this server at
|
|
||||||
=> gemini://glasgow.social/gemini-php
|
|
||||||
|
|
||||||
Join the community on Matrix at #gemini-php:glasgow.social
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
Let's take 'gemini.com' as an example.
|
|
||||||
* Generate a certificate and place it in the 'certs' directory named gemini.com.pem
|
|
||||||
* Create a directory to server files from in the 'hosts' directory named gemini.com (e.g. mkdir hosts/gemini.com)
|
|
||||||
* A file called index.gemini will be served if you haven't specified a path (this file is located at hosts/default/index.gemini)
|
|
||||||
|
|
||||||
=> natalie.jpg
|
|
20
server.php
20
server.php
@ -4,8 +4,9 @@
|
|||||||
* Version 0.1, Oct 2020
|
* Version 0.1, Oct 2020
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if(!require("config.php"))
|
if(!require("config.php")) {
|
||||||
die("config.php is missing. Copy config.php.sample to config.php and customise your settings");
|
die("config.php is missing. Copy config.php.sample to config.php and customise your settings");
|
||||||
|
}
|
||||||
require("gemini.class.php");
|
require("gemini.class.php");
|
||||||
$g = new Gemini($config);
|
$g = new Gemini($config);
|
||||||
|
|
||||||
@ -25,6 +26,10 @@ $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER
|
|||||||
& ~ STREAM_CRYPTO_METHOD_TLSv1_0_SERVER
|
& ~ STREAM_CRYPTO_METHOD_TLSv1_0_SERVER
|
||||||
& ~ STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
|
& ~ STREAM_CRYPTO_METHOD_TLSv1_1_SERVER;
|
||||||
|
|
||||||
|
$cryptoMethod = STREAM_CRYPTO_METHOD_TLSv1_3_SERVER;
|
||||||
|
|
||||||
|
print("Running server on port $g->port\n");
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
$forkedSocket = stream_socket_accept($socket, "-1", $remoteIP);
|
$forkedSocket = stream_socket_accept($socket, "-1", $remoteIP);
|
||||||
|
|
||||||
@ -42,17 +47,18 @@ while(true) {
|
|||||||
$meta = "";
|
$meta = "";
|
||||||
$filesize = 0;
|
$filesize = 0;
|
||||||
|
|
||||||
if($status_code == "20") {
|
if ($status_code == "20") {
|
||||||
$meta = $g->get_mime_type($filepath);
|
$meta = $g->get_mime_type($filepath);
|
||||||
$content = file_get_contents($filepath);
|
$content = file_get_contents($filepath);
|
||||||
$filesize = filesize($filepath);
|
$filesize = filesize($filepath);
|
||||||
} else {
|
} else {
|
||||||
$meta = "Not found";
|
$meta = "Not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
$status_line = $status_code." ".$meta;
|
$status_line = $status_code . " " . $meta;
|
||||||
if($g->logging)
|
if ($g->logging) {
|
||||||
$g->log_to_file($remoteIP,$status_code, $meta, $filepath, $filesize);
|
$g->log_to_file($remoteIP, $status_code, $meta, $filepath, $filesize);
|
||||||
|
}
|
||||||
$status_line .= "\r\n";
|
$status_line .= "\r\n";
|
||||||
fwrite($forkedSocket, $status_line);
|
fwrite($forkedSocket, $status_line);
|
||||||
|
|
||||||
@ -62,5 +68,3 @@ while(true) {
|
|||||||
|
|
||||||
fclose($forkedSocket);
|
fclose($forkedSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user