From 9f81b8964a1262005d1203e3be8bde8d90fb906c Mon Sep 17 00:00:00 2001 From: Juhani Haverinen Date: Sat, 6 Dec 2014 22:37:45 +0200 Subject: [PATCH] First commit --- README | 5 ++ UNLICENSE | 24 +++++++ gophersrv.py | 172 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 README create mode 100644 UNLICENSE create mode 100644 gophersrv.py diff --git a/README b/README new file mode 100644 index 0000000..f1b3d3c --- /dev/null +++ b/README @@ -0,0 +1,5 @@ +It serves gopher, duh. +Modify lines 37 and 38 to configure. +Will automatically attempts to execute anything with execute bit (except directories). +Requires Python 2, as Python 3's string handling it just weird. +Also responds to HTTP queries. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..69843e4 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to [http://unlicense.org] diff --git a/gophersrv.py b/gophersrv.py new file mode 100644 index 0000000..404460f --- /dev/null +++ b/gophersrv.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# Software written by Juhani Haverinen (nortti). Influenced in idea and +# some implementation details by https://github.com/puckipedia/pyGopher/ +# ----------------------------------------------------------------------- +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# ----------------------------------------------------------------------- +# NOTE: Requires python 2 due to python 3's braindeadness. + +import os +import socket +import stat +import subprocess +import threading + +# Config +port = 7070 +gopherroot = os.environ['HOME']+'/gopher' + +# Set up socket +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.bind(('', port)) +sock.listen(1) + +# Helper functions +def exist(path): + return os.access(path, os.F_OK) + +def isdir(path): + st = os.stat(path) + return stat.S_ISDIR(st.st_mode) + +def isexecutable(path): + return os.access(path, os.X_OK) + +def normalizepath(path): + path = path.split('/') + while '' in path: + path.remove('') + + while '..' in path: + i = path.index('..') + if i == 0: + return None # Attempted to access something outside gopherroot + path = path[:i-1] + path[i+1:] + + return '/'.join(path) + +# Error handling +def notfounderror(conn, path): + conn.sendall('3"%s" does not exist\t/\t(null)\t0\n' % path) +def notallowederror(conn, path): + conn.sendall('3Access denied\t/\t(null)\t0\n') + +# Server implementation +def getrequest(conn): + data = '' + while True: + chunk = conn.recv(1024) + + if not chunk: + return None + + data += chunk + + if data[-1] == '\n': + break + + while len(data) > 0 and data[-1] in ('\r', '\n'): + data = data[:-1] + + # Minimal HTTP support + if len(data) >= 4 and data[:4] == 'GET ': + data = data.split()[1] + + return data.split('\t') + +def serveurlredirect(conn, path): + path = path[4:] + conn.sendall('\n' + '\n' + '\t\n' + '\t\t\n' + '\t\n' + '\t\n' + '\t\t

Redirect to %s

\n' + '\t\n' + '' % (path, path, path)) + +def servecommon(conn, fd): + for line in fd: + conn.sendall(line) + + fd.close() + +def servecgi(conn, path): + proc = subprocess.Popen([path], stdout=subprocess.PIPE) + servecommon(conn, proc.stdout) + +def servefile(conn, path): + f = open(path, 'r') + servecommon(conn, f) + +def serverequest(conn, request): + # URL link extension + if len(request[0]) >= 4 and request[0][:4] == 'URL:': + return serveurlredirect(conn, request[0]) + + reqpath = normalizepath(request[0]) + + if reqpath == None: + return notallowederror(conn, reqpath) + + path = gopherroot + '/' + reqpath + + if not exist(path): + return notfounderror(conn, reqpath) + + if isdir(path): + if exist(path + '/gophermap'): + path = path + '/gophermap' + else: + return notfounderror(conn, reqpath) + + if isexecutable(path): + servecgi(conn, path) + else: + servefile(conn, path) + +class Serve(threading.Thread): + def __init__(self, conn): + self.conn = conn + threading.Thread.__init__(self) + def run(self): + try: + request = getrequest(self.conn) + + if not request: + self.conn.shutdown(socket.SHUT_RDWR) + self.conn.close() + return + + serverequest(self.conn, request) + + self.conn.shutdown(socket.SHUT_RDWR) + self.conn.close() + except socket.error: + self.conn.close() + +while True: + conn, addr = sock.accept() + Serve(conn).start()