From 87ec282748ef520d5541aa48d754e6a3fcd1fffe Mon Sep 17 00:00:00 2001 From: Malte Bublitz Date: Thu, 22 Jan 2026 14:20:42 +0100 Subject: [PATCH] =?utf8?q?=E2=9C=A8=20Multiple=20new=20features=20for=20mf?= =?utf8?q?ingerd=20to=20get=20ready=20for=20the=20SmolWeb?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- bbs/mfingerd.py | 2 +- bin/finger-inetd | 112 +++++++++++++++++++++++++++++- bin/qotd.py | 78 +++++++++++++++++++++ finger-smallweb/README.txt | 3 + finger-smallweb/rfcs/rfc1149.txt | 115 +++++++++++++++++++++++++++++++ 5 files changed, 308 insertions(+), 2 deletions(-) create mode 100755 bin/qotd.py create mode 100644 finger-smallweb/README.txt create mode 100644 finger-smallweb/rfcs/rfc1149.txt diff --git a/bbs/mfingerd.py b/bbs/mfingerd.py index 9d39ec0..f542752 100644 --- a/bbs/mfingerd.py +++ b/bbs/mfingerd.py @@ -64,7 +64,7 @@ def fingerinfo(username, client=None): # Replace placeholders like %%IP%% or %%BOFH%%. info = replace_placeholders(info, client) - print(info) + return info class FingerHandler(socketserver.StreamRequestHandler): diff --git a/bin/finger-inetd b/bin/finger-inetd index c37c603..ec9d44e 100755 --- a/bin/finger-inetd +++ b/bin/finger-inetd @@ -9,13 +9,26 @@ import os import sys +import glob + +# +# Add the Git project root to Python's sys.path, +# and make it the current working directory. +# +# When invoked by inetd, this is required for all +# code find below these lines. +# BBS = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, BBS) os.chdir(BBS) + +# Now we can import mfingerd import bbs.mfingerd + +# try: username = sys.stdin.readline().strip() except UnicodeDecodeError: @@ -23,6 +36,10 @@ except UnicodeDecodeError: print("???") sys.exit(0) +# +# inetd passes the client IP to us using an environment +# variable +# remote_ip = os.getenv("TCPREMOTEIP", None) @@ -35,5 +52,98 @@ for env in sorted(list(os.environ.keys())): # }}}""" -bbs.mfingerd.fingerinfo(username, remote_ip) +# +# rfc/ - Serve some selected RFCs in plain text format +# +RFC_PATH = "/opt/bbs/finger-smallweb/rfcs" +HOSTNAME = "giesskanne.flachbaum.de" + +def get_rfc_filenames_sorted(rfc_path: str, glob_expression = "rfc*.txt"): + rfc_files = glob.glob(os.path.join(rfc_path, glob_expression)) + rfc_files = sorted( + rfc_files, + key = lambda x: int( + x.split("/")[-1].replace("rfc","").replace(".txt","") + ) + ) + return rfc_files + + +if username.split("/")[0] == "rfc": + rfc_id = username.rstrip("/").split("/") + if len(rfc_id) > 1: + rfc_id = rfc_id[1] + else: + rfc_id = "-1" + + try: + if str(int(rfc_id)) != rfc_id: + print("rfc :: Invalid RFC ID!") + sys.exit(0) + except: + print("EXCEPTION") + sys.exit(0) + + if int(rfc_id) >= 1: + rfc_file = os.path.join( + RFC_PATH, + "rfc" + str(int(rfc_id)) + ".txt" + ) + + if not os.path.exists(rfc_file): + print("RFC " + rfc_id + " now available yet on this service.") + sys.exit(0) + + with open(rfc_file) as rfc: + contents = "" + for l in rfc.readlines(): + l = l.rstrip("\n").replace("\x0c", "\n\n\n\n\n") + contents += l + "\n" + + else: + contents = "" + contents += "List of RFCs viewable on this server:\n" + contents += "\n" + + for rfc_file in get_rfc_filenames_sorted(RFC_PATH): + rfc_id = rfc_file.split("/")[-1].replace("rfc","").replace(".txt","") + contents += "finger://" + contents += HOSTNAME + contents += "/rfc/" + str(rfc_id) + contents += "\n" + + contents += "\n\n" + contents += "More documents might be available in the future.\n" + + print(contents) + + sys.exit(0) + + +# +# qotd.py - Serve my QOTD client over finger:// +# +if username == "qotd.py": + script_filename = "bin/qotd.py" + try: + with open(script_filename, "r") as f: + content = f.read() + + except FileNotFoundError: + print("Server Error: File not found.") + sys.exit() + + except PermissionError: + print("Server Error: Premission denied.") + sys.exit() + + print(content) + sys.exit() + + +# +# Display fingerinfo from database +# +print(bbs.mfingerd.fingerinfo(username, remote_ip)) + diff --git a/bin/qotd.py b/bin/qotd.py new file mode 100755 index 0000000..9a05651 --- /dev/null +++ b/bin/qotd.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +__doc__ = """A basic QOTD client. + +Use socket.socket to get a qoute of the day +from a remote server. + +Uses my own QOTD server by default, but you +can set your own hostname and port using +environment variables: + +- $QOTD_HOST + - Defaults to "giesskanne.flachbaum.de" +- $QOTD_PORT + - Defaults to 17 +""" + +import os +import sys +import socket + + +QOTD_HOST = "giesskanne.flachbaum.de" +QOTD_PORT = 17 + + +def get_qotd( + remote_addr: tuple[str, int] = (QOTD_HOST, QOTD_PORT), + buffer_size: int = 64, + read_limit: int | None = 2048 + ) -> str: + """Retrieve a qoute from a QOTD server. + + Args: + remote_addr (tuple[str, int]): Remote host and port number. + + Returns: + str: The quote recieved from `remote_addr` + + """ + s = socket.socket( + socket.AF_INET, + socket.SOCK_STREAM + ) + s.connect(remote_addr) + + qotd = "" + while True: + data = s.recv(buffer_size) + if not data or data is None: + break + qotd += data.decode("UTF-8") + s.close() + + return qotd + + +def main(): + # Default host and port from environment variables, falls back to + # `QOTD_HOST` and `QOTD_PORT` defined above. + qotd_remote_server = ( + os.getenv("QOTD_HOST", QOTD_HOST), + int(os.getenv("QOTD_PORT", QOTD_PORT)) + ) + try: + print(get_qotd(qotd_remote_server)) + + except ConnectionRefusedError: + _remote = qotd_remote_server[0] + ":" + str(qotd_remote_server[1]) + print(f"{os.path.basename(__file__)}: Error: Connection refused " + f"to {_remote}", file=sys.stderr) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/finger-smallweb/README.txt b/finger-smallweb/README.txt new file mode 100644 index 0000000..e50ac5f --- /dev/null +++ b/finger-smallweb/README.txt @@ -0,0 +1,3 @@ +# Host a finger:// smallweb page using bbs.mfingerd + + diff --git a/finger-smallweb/rfcs/rfc1149.txt b/finger-smallweb/rfcs/rfc1149.txt new file mode 100644 index 0000000..a981394 --- /dev/null +++ b/finger-smallweb/rfcs/rfc1149.txt @@ -0,0 +1,115 @@ + + + + + + +Network Working Group D. Waitzman +Request for Comments: 1149 BBN STC + 1 April 1990 + + + A Standard for the Transmission of IP Datagrams on Avian Carriers + +Status of this Memo + + This memo describes an experimental method for the encapsulation of + IP datagrams in avian carriers. This specification is primarily + useful in Metropolitan Area Networks. This is an experimental, not + recommended standard. Distribution of this memo is unlimited. + +Overview and Rational + + Avian carriers can provide high delay, low throughput, and low + altitude service. The connection topology is limited to a single + point-to-point path for each carrier, used with standard carriers, + but many carriers can be used without significant interference with + each other, outside of early spring. This is because of the 3D ether + space available to the carriers, in contrast to the 1D ether used by + IEEE802.3. The carriers have an intrinsic collision avoidance + system, which increases availability. Unlike some network + technologies, such as packet radio, communication is not limited to + line-of-sight distance. Connection oriented service is available in + some cities, usually based upon a central hub topology. + +Frame Format + + The IP datagram is printed, on a small scroll of paper, in + hexadecimal, with each octet separated by whitestuff and blackstuff. + The scroll of paper is wrapped around one leg of the avian carrier. + A band of duct tape is used to secure the datagram's edges. The + bandwidth is limited to the leg length. The MTU is variable, and + paradoxically, generally increases with increased carrier age. A + typical MTU is 256 milligrams. Some datagram padding may be needed. + + Upon receipt, the duct tape is removed and the paper copy of the + datagram is optically scanned into a electronically transmittable + form. + +Discussion + + Multiple types of service can be provided with a prioritized pecking + order. An additional property is built-in worm detection and + eradication. Because IP only guarantees best effort delivery, loss + of a carrier can be tolerated. With time, the carriers are self- + + + +Waitzman [Page 1] + +RFC 1149 IP Datagrams on Avian Carriers 1 April 1990 + + + regenerating. While broadcasting is not specified, storms can cause + data loss. There is persistent delivery retry, until the carrier + drops. Audit trails are automatically generated, and can often be + found on logs and cable trays. + +Security Considerations + + Security is not generally a problem in normal operation, but special + measures must be taken (such as data encryption) when avian carriers + are used in a tactical environment. + +Author's Address + + David Waitzman + BBN Systems and Technologies Corporation + BBN Labs Division + 10 Moulton Street + Cambridge, MA 02238 + + Phone: (617) 873-4323 + + EMail: dwaitzman@BBN.COM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Waitzman [Page 2] + \ No newline at end of file -- 2.47.3