malte70.blog()

QOTD-Protokoll – Quote of the Day

Ich habe mich schon seit langem für die Anfänge des Internets bzw. allgemein für das alte, einfachere Internet vor dem Siegeszug des Web 2.0 interessiert. Gerade die ganz alten Protokolle, wie Quote of the Day, gefallen mir, da sie zur Unix-Philosophie passen und ohne Aufwand z.B. in einer Pipe verwendet werden können.

Das Quote of the Day-Protokoll ist eins dieser Relikte aus längst vergessenen Zeiten des Internets. Bei diesem in RFC 865 beschriebenen Protokoll wird nach dem Verbindungsaufbau ein zufälliges Zitat an den Client gesendet, und die Verbindung danach direkt getrennt.

Laut RFC muss der Dienst via TCP- und UDP-Port 17 erreichbar sein, und Daten mit ASCII-Charset senden.

Einen von mir betriebenen, nicht ganz RFC-konformen (nur TCP) QOTD-Dienst kannst du z.B. via Netcat testen:

$ nc -w 1 malte70.de 17
Wenn mir Altavista in Reaktion auf meinen Suchbegriff eine
Senioren-Kontakt-Boerse als Bannerwerbung anbietet...
Wie ernsthaft muss ich mir dann Gedanken machen?
                -- #LinuxGER

$ 

QOTD-Dienst via inetd betreiben

Unter Raspbian kann inetd (Teil der GNU inetutils) folgendermaßen installiert werden:

sudo apt install inetutils-inetd

Danach muss der QOTD-Dienst in der /etc/inetd.conf eingerichtet werden:

#:INFO: Info services
qotd  stream  tcp4  nowait  nobody  /usr/local/bin/qotd

Im Skript /usr/local/bin/qotd wird nun fortune aufgerufen, und die Ausgabe bereinigt, um ASCII-konform zu bleiben:

#!/bin/bash
#
# See also:
#     RFC 865 - Quote of the Day Protocol
#

PATH=/usr/games:$PATH

fortune                 \
	letzteworte         \
	ms                  \
	murphy              \
	namen               \
	quiz                \
	regeln              \
	sicherheitshinweise \
	sprueche            \
	stilblueten         \
	tips                \
	translations        \
	unfug               \
	vornamen            \
	witze               \
	woerterbuch         \
	wusstensie          \
	zitate              \
	| sed \
		-e's/Ä/Ae/g' \
		-e's/Ö/Oe/g' \
		-e's/Ü/Ue/g' \
		-e's/ä/ae/g' \
		-e's/ö/oe/g' \
		-e's/ü/ue/g' \
		-e's/ß/ss/g' \
	| iconv -f UTF-8 -t US-ASCII

Nach einem Neustart von inetd ist der Dienst nun erreichbar.

$ systemctl restart inetutils-inetd.service
$ nc -w 1 localhost 17
Wenn mir Altavista in Reaktion auf meinen Suchbegriff eine
Senioren-Kontakt-Boerse als Bannerwerbung anbietet...
Wie ernsthaft muss ich mir dann Gedanken machen?
                -- #LinuxGER

$ 

Da hierbei für jede Verbindung ein Prozess gestartet wird, sollte der Dienst über eine Firewall vor einem Denial of Service-Angriff abgesichert werden! Im Falle von UFW lässt sich ein Port mit Rate limiting wie folgt öffnen:

sudo ufw limit 17/tcp comment "Quote of the Day"

Python-Client

Mithilfe der integrierten Bibliothek socket lässt sich relativ einfach ein Python-Client implementieren. Dieser kann z.B. bei jedem Login in der Shell ein Zitat anzeigen, ohne dass fortune-mod sowie vor allem die entsprechenden Cookie-Datenbanken lokal installiert sein müssen.

#!/usr/bin/env python3

import os
import sys
import socket


# Voreingestellter Host
QOTD_HOST = "malte70.de"
# Port laut RFC
QOTD_PORT = 17


def get_qotd(remote_addr: tuple[str, int]) -> 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`
	
	"""
	# Connect to `remote_addr`
	s = socket.socket(
		socket.AF_INET,
		socket.SOCK_STREAM
	)
	s.connect(remote_addr)

	# Read from socket
	qotd = ""
	while True:
		data = s.recv(64)
		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, fals 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 = f"{qotd_remote_server[0]}:{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())

Der voreingestellte Server und Port kann via Environment-Variable angepasst werden:

# Mein QOTD-Dienst
./qotd.py

# Lokaler Dienst auf Port 10017
env QOTD_HOST=localhost QOTD_PORT=10017 ./qotd.py