As I detailed in my other post, the Finger protocol is extremely simple and easy to implement. But I didn't show any details on how to actually implement it! A while back I wrote this test server/client pair in Python 3, so I will print the code here for posterity.
The following code snippets are licensed as CC0/Public Domain, and I claim no ownership or responsibility thereof.
finger_client.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket, sys
def print_help():
print("python3 finger_client.py username@hostname")
sys.exit(0)
if __name__ == "__main__":
if len(sys.argv) != 2:
print_help()
args = sys.argv[1].split("@")
if not args or len(args) != 2:
print_help()
username = args[0]
hostname = args[1]
try:
s = socket.create_connection((hostname, 79), 5)
except OSError as msg:
print(msg, file=sys.stderr)
sys.exit(1)
s.sendall(f"{username}\r\n".encode("ascii"))
plan = s.recv(8192)
print(f"[{hostname}]")
print(plan.decode("ascii"), end="")
s.shutdown(socket.SHUT_RDWR)
s.close()
finger_server.py
Some notes:
- The
get_plan()
function is not actually implemented here. I'm leaving that as an exercise to the reader, but needless to say it should safely and securely access the server's filesystem to look for the appropriate file to return to the Finger client. - This could easily be expanded with the features of other Finger servers, i.e.
prefixing or postfixing the
.plan
file with information about the user, or pick from a random list of "witty phrases", etc. - I was originally going to implement this in C, but the BSD socket API was giving me a headache, so I didn't. I know that the Python socket API is built on top of it, but naturally it was much simpler to spin up something simple like this (The Finger protocol over IPv6 still makes me laugh, though).
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket, sys
PORT=79
TIMEOUT=15
MAX_CONNECTS=10
def get_plan(username, verbose):
return f"username={username} verbose={verbose}"
if __name__ == "__main__":
try:
addr = ("", PORT)
if socket.has_dualstack_ipv6():
s = socket.create_server(addr, family=socket.AF_INET6, dualstack_ipv6=True)
else:
s = socket.create_server(addr, backlog=MAX_CONNECTS)
except OSError as msg:
print(msg, file=sys.stderr)
sys.exit(1)
print(f"Now listening on port: {PORT}\nConnection timeout: {TIMEOUT} seconds\nMax simultaneous connections: {MAX_CONNECTS}")
while True:
try:
conn, addr = s.accept()
with conn:
conn.settimeout(TIMEOUT)
print(f"Connected by: {addr[0]}:{addr[1]}")
while True:
command = conn.recv(128)
if not command: break
print(f"Finger command: {repr(command)}")
cmd = command.decode("ascii").strip()
if cmd[:3].casefold() == "/W ".casefold():
username = cmd[3:]
verbose = True
else:
username = cmd
verbose = False
plan = get_plan(username, verbose)
conn.sendall(f"{plan}\n\n".encode("ascii"))
conn.shutdown(socket.SHUT_RDWR)
conn.close()
break
except KeyboardInterrupt:
print("Shutting down")
break
s.shutdown(socket.SHUT_RDWR)
s.close()