Add support for cgi scripts

main
Micke Nordin 4 years ago
parent 0e14141dde
commit faa5fcbb08

@ -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" ]

@ -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…
Cancel
Save