#!/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 import os import socket import stat import subprocess import threading import time # Config port = 7777 gopherroot = os.environ['HOME']+'/gopher' blacklistfile = os.environ['HOME']+'/gopher_blacklist_1' # Set up socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #DEBG 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 error(conn, ishttp, text, code): sendheader(conn, ishttp, '1', code) if ishttp: conn.sendall('\n' '\t\n' '\t\t%s\n' '\t\n' '\t\n' '\t\t

%s

\n' '\t\n' '' % (code, text)) else: conn.sendall('3%s\t/\t(null)\t0\n' % text) def notfounderror(conn, path, ishttp): error(conn, ishttp, '"%s" does not exist' % path, '404 Not Found') def notallowederror(conn, path, ishttp): error(conn, ishttp, 'Access denied', '403 Forbidden') # Server implementation def getrequest(conn): ishttp = False data = '' while True: chunk = conn.recv(1024) if not chunk: return None, ishttp 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] ishttp = True return data.split('\t'), ishttp def getselector(request): # If a HTTP request with selector is used, this extracts the selector if len(request) < 1: return (request, None) req = request[0] if len(req) >= 1 and req[0] == '/': req = req[1:] args = request[1:] if len(req) >= 1 and req[0] in ['0', '1', '5', '9', 'g', 'h', 'I', 's']: # Supported selectors reqpath = req[1:] selector = req[0] elif len(req) == 0: # Root is by default of type 1 reqpath = '/' selector = '1' else: reqpath = req selector = None return ([reqpath] + args, selector) def sendheader(conn, ishttp, selector, code = '200 OK'): if ishttp: # All others can safely be made text/plain contenttypes = {'1': 'text/html; charset=utf-8', '5': 'application/octet-stream', '9': 'application/octet-stream', 'g': 'image/gif', 'h': 'text/html; charset=utf-8', 'I': 'application/octet-stream', 's': 'application/octet-stream'} if selector is not None and selector in contenttypes: contenttype = contenttypes[selector] else: contenttype = 'text/plain; charset=utf-8' # Default to text/plain conn.sendall('HTTP/1.1 %s\r\n' 'Content-type: %s\r\n' '\r\n' % (code, contenttype)) 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 servehtmlgophermap(conn, fd): conn.sendall('\n' '\n' '\t\n' '\t\tGophermap\n' '\t\n' '\t\n' '\t\t

\n' '\t\t\t.. /
\n') for line in fd: while len(line) > 0 and line[-1] == '\n': line = line[:-1] if line != '.' and line != '': text, path, server, port = line.split('\t') port = int(port) selector, text = text[0], text[1:] if selector == 'i' or selector == '3': conn.sendall('\t\t\t%s
\n' % text) else: if len(path) >= 4 and path[:4] == 'URL:': conn.sendall('\t\t\t%s
\n' % (path[4:], text)) else: conn.sendall('\t\t\t%s
\n' % (server, port, selector, path, text)) conn.sendall('\t\t

\n' '\t\n' '') def servecgi(conn, path, servefunc = servecommon): proc = subprocess.Popen([path], stdout=subprocess.PIPE) servefunc(conn, proc.stdout) def servefile(conn, path, servefunc = servecommon): f = open(path, 'r') servefunc(conn, f) def serverequest(conn, request, ishttp): # Extract selector if needed if ishttp: request, selector = getselector(request) else: selector = None # 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, ishttp) path = gopherroot + '/' + reqpath if not exist(path): return notfounderror(conn, reqpath, ishttp) if isdir(path): if exist(path + '/gophermap'): path = path + '/gophermap' else: return notfounderror(conn, reqpath, ishttp) sendheader(conn, ishttp, selector) if ishttp and selector == '1': servefunc = servehtmlgophermap else: servefunc = servecommon if isexecutable(path): servecgi(conn, path, servefunc) else: servefile(conn, path, servefunc) class Serve(threading.Thread): def __init__(self, conn): self.conn = conn threading.Thread.__init__(self) def run(self): try: (request, ishttp) = getrequest(self.conn) if not request: self.conn.shutdown(socket.SHUT_RDWR) self.conn.close() return serverequest(self.conn, request, ishttp) self.conn.shutdown(socket.SHUT_RDWR) self.conn.close() except socket.error: self.conn.close() def toint(addr): a1, a2, a3, a4 = [int(i) for i in addr.split('.')] return a1<<24 | a2<<16 | a3<<8 | a4 try: f = open(blacklistfile, 'r') except IOError: blacklist = [] else: blacklist = [] for line in f: if len(line) > 0 and line[-1] == '\n': line = line[:-1] line = line.split('/') if len(line) == 1: addr = toint(line[0]) upto = 32 elif len(line) == 2: addr = toint(line[0]) upto = int(line[1]) else: assert(not 'Invalid line format') blacklist.append((addr, upto)) f.close() def matchaddr(addr, blacklist_entry): blacklist_addr, upto = blacklist_entry shift = 32 - upto return addr >> shift == blacklist_addr >> shift while True: conn, addr = sock.accept() ip, port = addr if not any(map(lambda x: matchaddr(toint(ip), x), blacklist)): Serve(conn).start() else: print '%s: Blacklisted IP %s' % (time.strftime('%Y-%m-%d %H:%M'), ip) conn.close()