Add support for cgi scripts
This commit is contained in:
parent
0e14141dde
commit
faa5fcbb08
4 changed files with 67 additions and 7 deletions
|
@ -19,7 +19,8 @@ RUN pip install pyyaml
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY ./gmnd/__init__.py .
|
COPY ./gmnd/__init__.py .
|
||||||
COPY ./content content
|
COPY ./content content
|
||||||
|
COPY ./cgi-bin cgi-bin
|
||||||
COPY config.yml .
|
COPY config.yml .
|
||||||
COPY --from=build-stage /app/certs certs
|
COPY --from=build-stage /app/certs certs
|
||||||
EXPOSE 1965
|
EXPOSE 1965
|
||||||
CMD ["python", "__init__.py", "--file", "config.yml" ]
|
CMD ["python", "__init__.py", "--file", "./config.yml" ]
|
||||||
|
|
10
README.md
10
README.md
|
@ -1,7 +1,7 @@
|
||||||
# gMNd
|
# gMNd
|
||||||
gMNd is my gemini server, which is written in python. Documentation will primarily be supplied via [gemini://mic.ke/gmnd/docs](gemini://mic.ke/gmnd/docs), but if you are not yet able to access content via gemini, here is a quick start guide for your viewing pleasure:
|
gMNd is my gemini server, which is written in python. It has support for serving static files, or run cgi-scripts. Documentation will primarily be supplied via [gemini://mic.ke/gmnd/docs](gemini://mic.ke/gmnd/docs), but if you are not yet able to access content via gemini, here is a quick start guide for your viewing pleasure:
|
||||||
|
|
||||||
Currently it only serves static files. You can build and run it from the supplied Dockerfile if you so whish:
|
You can build and run it from the supplied Dockerfile if you so whish:
|
||||||
```
|
```
|
||||||
docker build -t gmnd:latest .
|
docker build -t gmnd:latest .
|
||||||
```
|
```
|
||||||
|
@ -9,11 +9,11 @@ By just running it, it will create self signed certs and serve example content f
|
||||||
```
|
```
|
||||||
docker run -p 1965:1965 gmnd
|
docker run -p 1965:1965 gmnd
|
||||||
```
|
```
|
||||||
A slightly more interesting thing it can do is serve your own content, in this example from /tmp/content on your host machine:
|
A slightly more interesting thing it can do is serve your own content, in this example from /tmp/content on your host machine and cgi-scripts from /tmp/cgi-bin:
|
||||||
```
|
```
|
||||||
docker run --mount type=bind,source="/tmp/content,target=/app/content" -p 1965:1965 gmnd
|
docker run --mount type=bind,source="/tmp/content,target=/app/content" --mount type=bind,source="/tmp/cgi-bin,target=/app/cgi-bin" -p 1965:1965 gmnd
|
||||||
```
|
```
|
||||||
Or even supply your own certificates from the outside, in this example in /usr/local/certs:
|
Or even supply your own certificates from the outside, in this example in /usr/local/certs with static content from /tmp/content:
|
||||||
```
|
```
|
||||||
docker run --mount type=bind,source="/tmp/content,target=/app/content" --mount type=bind,source="/usr/local/certs,target=/app/certs" -p 1965:1965 gmnd
|
docker run --mount type=bind,source="/tmp/content,target=/app/content" --mount type=bind,source="/usr/local/certs,target=/app/certs" -p 1965:1965 gmnd
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,3 +2,6 @@
|
||||||
allow_dir_list: true
|
allow_dir_list: true
|
||||||
listen_addr: '0.0.0.0'
|
listen_addr: '0.0.0.0'
|
||||||
logg_level: 'DEBUG'
|
logg_level: 'DEBUG'
|
||||||
|
#cgi_registry:
|
||||||
|
# /.*:
|
||||||
|
# - ./cgi-bin/envtest.sh
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import logging
|
import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
from socket import AF_INET, SHUT_RDWR, SO_REUSEADDR, SOCK_STREAM, SOL_SOCKET
|
from socket import AF_INET, SHUT_RDWR, SO_REUSEADDR, SOCK_STREAM, SOL_SOCKET
|
||||||
|
import subprocess
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -39,6 +41,8 @@ class gMNd:
|
||||||
self.allow_dir_list = config_dict['allow_dir_list']
|
self.allow_dir_list = config_dict['allow_dir_list']
|
||||||
if 'base_path' in config_dict:
|
if 'base_path' in config_dict:
|
||||||
self.base_path = config_dict['base_path']
|
self.base_path = config_dict['base_path']
|
||||||
|
if 'cgi_registry' in config_dict:
|
||||||
|
self.cgi_registry = config_dict['cgi_registry']
|
||||||
if 'listen_addr' in config_dict:
|
if 'listen_addr' in config_dict:
|
||||||
self.listen_addr = config_dict['listen_addr']
|
self.listen_addr = config_dict['listen_addr']
|
||||||
if 'listen_port' in config_dict:
|
if 'listen_port' in config_dict:
|
||||||
|
@ -67,6 +71,7 @@ class gMNd:
|
||||||
newsocket, fromaddr = self.bindsocket.accept()
|
newsocket, fromaddr = self.bindsocket.accept()
|
||||||
logging.debug("Client connected: {}:{}".format(
|
logging.debug("Client connected: {}:{}".format(
|
||||||
fromaddr[0], fromaddr[1]))
|
fromaddr[0], fromaddr[1]))
|
||||||
|
|
||||||
conn = ssl.wrap_socket(newsocket,
|
conn = ssl.wrap_socket(newsocket,
|
||||||
server_side=True,
|
server_side=True,
|
||||||
certfile=self.server_cert,
|
certfile=self.server_cert,
|
||||||
|
@ -86,7 +91,23 @@ class gMNd:
|
||||||
|
|
||||||
header = get_header()
|
header = get_header()
|
||||||
body = b""
|
body = b""
|
||||||
if os.path.isfile(self.base_path + path):
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
for key, val in self.cgi_registry.items():
|
||||||
|
if re.match(key, path):
|
||||||
|
cgi = True
|
||||||
|
env = self.get_env(url, fromaddr[0])
|
||||||
|
script = val
|
||||||
|
except:
|
||||||
|
cgi = False
|
||||||
|
script = None
|
||||||
|
env = None
|
||||||
|
|
||||||
|
if cgi:
|
||||||
|
body = run_cgi(script, env)[0]
|
||||||
|
|
||||||
|
elif os.path.isfile(self.base_path + path):
|
||||||
if not path.endswith(".gmi"):
|
if not path.endswith(".gmi"):
|
||||||
header = get_header(
|
header = get_header(
|
||||||
'20',
|
'20',
|
||||||
|
@ -133,6 +154,41 @@ class gMNd:
|
||||||
contents = contents + b"=> " + entry.encode() + b"\r\n"
|
contents = contents + b"=> " + entry.encode() + b"\r\n"
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
|
def get_env(self, url, remote_addr):
|
||||||
|
path = url.path.decode().rstrip()
|
||||||
|
query = url.query.decode().rstrip()
|
||||||
|
env = {}
|
||||||
|
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
||||||
|
env['GEMINI_DOCUMENT_ROOT'] = str(self.base_path)
|
||||||
|
env['GEMINI_URL'] = str(url.geturl().decode().rstrip())
|
||||||
|
env['GEMINI_URL_PATH'] = str(path)
|
||||||
|
env['PATH_INFO'] = str(path)
|
||||||
|
env['PATH_TRANSLATED'] = str(self.base_path + path)
|
||||||
|
env['QUERY_STRING'] = str(query)
|
||||||
|
env['REMOTE_ADDR'] = str(remote_addr)
|
||||||
|
try:
|
||||||
|
env['REMOTE_HOST'] = str(
|
||||||
|
socket.getfqdn(socket.gethostbyaddr(remote_addr)[0]))
|
||||||
|
except:
|
||||||
|
env['REMOTE_HOST'] = str(remote_addr)
|
||||||
|
env['SERVER_NAME'] = str(socket.getfqdn())
|
||||||
|
env['SERVER_PORT'] = str(self.listen_port)
|
||||||
|
env['SERVER_PROTOCOL'] = 'GEMINI'
|
||||||
|
env['SERVER_SOFTWARE'] = 'gMNd'
|
||||||
|
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
def run_cgi(script, menv):
|
||||||
|
"""Run a command on system and capture result"""
|
||||||
|
process = subprocess.Popen(script,
|
||||||
|
env=menv,
|
||||||
|
shell=True,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
result = process.communicate()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_header(status='20', meta=b"text/gemini"):
|
def get_header(status='20', meta=b"text/gemini"):
|
||||||
metadict = {}
|
metadict = {}
|
||||||
|
|
Loading…
Add table
Reference in a new issue