aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Touhey <thomas@touhey.fr>2021-09-04 17:18:41 +0200
committerThomas Touhey <thomas@touhey.fr>2021-09-04 17:18:41 +0200
commit99c8256289363fd437a1f350eeb42faf35a2a317 (patch)
tree31896a5fbbc323f49940ea06f4fa12f86848f006
parent5ee802842794f112977b7b9273e35f45f2bf8a88 (diff)
Continued documenting and stuff.
-rw-r--r--.env.template38
-rw-r--r--Pipfile1
-rw-r--r--docs/Makefile3
-rw-r--r--docs/Pipfile1
-rw-r--r--docs/Pipfile.lock82
-rw-r--r--docs/api.rst9
-rw-r--r--docs/api/config.rst231
-rw-r--r--docs/api/core.rst54
-rw-r--r--docs/cli.rst110
-rw-r--r--docs/conf.py15
-rw-r--r--docs/discuss.rst11
-rw-r--r--docs/discuss/fictional-interfaces.rst132
-rw-r--r--docs/discuss/fingerd-structure.rst36
-rw-r--r--docs/explain.rst6
-rw-r--r--docs/howto.rst7
-rw-r--r--docs/index.rst4
-rw-r--r--docs/onboarding.rst20
-rw-r--r--docs/onboarding/installing.rst41
-rw-r--r--docs/onboarding/running.rst81
-rw-r--r--docs/onboarding/tweaking.rst16
-rwxr-xr-xfingerd/__init__.py2
-rw-r--r--fingerd/cli.py7
-rwxr-xr-xfingerd/control/__init__.py182
-rwxr-xr-xfingerd/control/__main__.py16
-rwxr-xr-xfingerd/core.py (renamed from fingerd/server.py)548
-rwxr-xr-xfingerd/fiction.py14
-rw-r--r--fingerd/version.py2
-rw-r--r--setup.cfg2
28 files changed, 969 insertions, 702 deletions
diff --git a/.env.template b/.env.template
deleted file mode 100644
index 869fd37..0000000
--- a/.env.template
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/sh
-# FINGERD ENVIRONMENT
-# ===================
-# Address and port on which to open the server.
-# The BIND variable is required, otherwise old-style configuration is
-# assumed (although old-style configuration is deprecated).
-#
-# It is recommended to use another port than 79 as described in the README,
-# but the default port is still 79 as it is the standard port for
-# the finger protocol.
-
-BIND=localhost:79
-#FINGER_HOST=LOCALHOST
-
-# Interface type, i.e. what we actually want to have as the data behind the
-# server. There are three possible interface types:
-# - DUMMY: no users are connected.
-# - NATIVE: the data is gathered from the system.
-# - SCENARIO: the data is gathered from a scenario.
-# - LIVE: the data is gathered from a live source, here a nanomsg endpoint.
-
-#FINGER_TYPE=NATIVE
-
-# For actions, the scenario path must be given in the following variable,
-# absolute or relative to the current working directory.
-
-#FINGER_SCENARIO=actions.toml
-
-# For a live server receiving events, there must be an endpoint where
-# endpoints are received. This endpoint is using nanomsg's REQREP protocol,
-# so a nanomsg URI is expected, amongst:
-
-#FINGER_INCOMING=ipc:///var/run/fingerd.sock
-#FINGER_INCOMING=tcp://127.0.0.1:5560
-#FINGER_INCOMING=tcp://[::1]:5560
-#FINGER_INCOMING=tcp://localhost:5560
-
-# That's it, your done with the finger server configuration! :-)
diff --git a/Pipfile b/Pipfile
index 37e634e..d8c3cc5 100644
--- a/Pipfile
+++ b/Pipfile
@@ -8,7 +8,6 @@ python_version = '3.9'
[packages]
python-dotenv = '*'
-nanomsg = '*'
toml = '*'
click = '*'
diff --git a/docs/Makefile b/docs/Makefile
index 972c107..f754cdf 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -27,7 +27,8 @@ help:
# Livehtml build.
livehtml:
- $(SPHINXWATCH) -b html $(SPHINXOPTS) . $(BUILDDIR)/html
+ $(SPHINXWATCH) -b html $(SPHINXOPTS) . $(BUILDDIR)/html \
+ --ignore "**/.*.kate-swp" --watch ../fingerd
.PHONY: livehtml
diff --git a/docs/Pipfile b/docs/Pipfile
index bca103b..efdb5a1 100644
--- a/docs/Pipfile
+++ b/docs/Pipfile
@@ -9,6 +9,7 @@ verify_ssl = true
sphinx = "*"
sphinx-rtd-theme = "*"
sphinx-autobuild = "*"
+sphinx-autodoc-typehints = "*"
[requires]
python_version = "3.9"
diff --git a/docs/Pipfile.lock b/docs/Pipfile.lock
index 9003dd3..6a33722 100644
--- a/docs/Pipfile.lock
+++ b/docs/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "94b3326dd69ba0924a8e8ba1a51f1972bceebacbd4daa2f0c7200a78f86e7f35"
+ "sha256": "9e53fd6e9c8d06e445ecf8cdf0d568ef01a8e987bb88ea841878ddf582def9d1"
},
"pipfile-spec": 6,
"requires": {
@@ -38,13 +38,13 @@
],
"version": "==2021.5.30"
},
- "chardet": {
+ "charset-normalizer": {
"hashes": [
- "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
- "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
+ "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
+ "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==4.0.0"
+ "markers": "python_version >= '3'",
+ "version": "==2.0.4"
},
"colorama": {
"hashes": [
@@ -64,11 +64,11 @@
},
"idna": {
"hashes": [
- "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
- "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
+ "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
+ "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.10"
+ "markers": "python_version >= '3'",
+ "version": "==3.2"
},
"imagesize": {
"hashes": [
@@ -99,30 +99,50 @@
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
+ "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724",
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
+ "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646",
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
+ "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6",
+ "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6",
+ "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad",
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
+ "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38",
+ "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac",
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
+ "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6",
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
+ "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a",
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
+ "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9",
+ "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864",
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
+ "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b",
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
+ "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28",
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
+ "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d",
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
+ "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145",
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
+ "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c",
+ "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1",
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
+ "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53",
+ "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134",
+ "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85",
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
@@ -134,19 +154,19 @@
},
"packaging": {
"hashes": [
- "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
- "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
+ "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
+ "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==20.9"
+ "markers": "python_version >= '3.6'",
+ "version": "==21.0"
},
"pygments": {
"hashes": [
- "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
- "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
+ "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
+ "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
],
"markers": "python_version >= '3.5'",
- "version": "==2.9.0"
+ "version": "==2.10.0"
},
"pyparsing": {
"hashes": [
@@ -165,11 +185,11 @@
},
"requests": {
"hashes": [
- "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
- "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
+ "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
+ "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==2.25.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+ "version": "==2.26.0"
},
"six": {
"hashes": [
@@ -188,11 +208,11 @@
},
"sphinx": {
"hashes": [
- "sha256:b5c2ae4120bf00c799ba9b3699bc895816d272d120080fbc967292f29b52b48c",
- "sha256:d1cb10bee9c4231f1700ec2e24a91be3f3a3aba066ea4ca9f3bbe47e59d5a1d4"
+ "sha256:3092d929cd807926d846018f2ace47ba2f3b671b309c7a89cd3306e80c826b13",
+ "sha256:46d52c6cee13fec44744b8c01ed692c18a640f6910a725cbb938bc36e8d64544"
],
"index": "pypi",
- "version": "==4.0.2"
+ "version": "==4.1.2"
},
"sphinx-autobuild": {
"hashes": [
@@ -202,6 +222,14 @@
"index": "pypi",
"version": "==2021.3.14"
},
+ "sphinx-autodoc-typehints": {
+ "hashes": [
+ "sha256:193617d9dbe0847281b1399d369e74e34cd959c82e02c7efde077fca908a9f52",
+ "sha256:5e81776ec422dd168d688ab60f034fccfafbcd94329e9537712c93003bddc04a"
+ ],
+ "index": "pypi",
+ "version": "==1.12.0"
+ },
"sphinx-rtd-theme": {
"hashes": [
"sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a",
@@ -307,11 +335,11 @@
},
"urllib3": {
"hashes": [
- "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c",
- "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"
+ "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
+ "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
- "version": "==1.26.5"
+ "version": "==1.26.6"
}
},
"develop": {}
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000..f257cca
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,9 @@
+API Reference
+=============
+
+If you are looking for information on a specific function, class or method,
+this part of the documentation is for you.
+
+.. toctree::
+
+ api/core
diff --git a/docs/api/config.rst b/docs/api/config.rst
deleted file mode 100644
index 978d848..0000000
--- a/docs/api/config.rst
+++ /dev/null
@@ -1,231 +0,0 @@
-Configuring fingerd
-===================
-
-``fingerd`` stores its configuration in the environment. For commodity,
-a ``.env`` configuration is proposed, install the ``python-dotenv`` module
-and copy the ``.env.template`` file into ``.env`` and edit it following
-its comments.
-
-``BIND``
- This variable is mandatory and contains the endpoints to bind on,
- separated with commas (``,``). Each endpoint can have the following
- format:
-
- ``example.com``
- Bind on all addresses that ``example.com`` resolve as (IPv4 and IPv6),
- port 79.
-
- ``example.com:1234``
- Bind on all addresses that ``example.com`` resolve as (IPv4 and IPv6),
- port 1234.
-
- ``1.2.3.4`` or ``[1.2.3.4]``
- Bind on ``1.2.3.4`` (IPv4), port 79.
-
- ``1.2.3.4:1234`` or ``[1.2.3.4]:1234``
- Bind on ``1.2.3.4`` (IPv4), port 1234.
-
- ``::1:2:3:4`` or ``[::1:2:3:4]``
- Bind on ``::1:2:3:4`` (IPv6) port 79.
-
- ``[::1:2:3:4]:1234``
- Bind on ``::1:2:3:4`` (IPv6) port 1234.
-
- Here are some examples:
-
- .. code-block:: bash
-
- # On modern platforms, binds on 127.0.0.1:79 (IPv4)
- # and [::1]:79 (IPv6).
- BIND=localhost
-
- # Binds on 1.2.3.4:3331 and [2001:41d0:302:2200::3b2]:79.
- BIND="1.2.3.4:3331,[2001:41d0:302:2200::3b2]:79"
- BIND="1.2.3.4:3331,2001:41d0:302:2200::3b2"
-
-``FINGER_HOST``
- The host as which fingerd identifies itself (domain name), ``LOCALHOST``
- by default.
-
-``FINGER_TYPE``
- The interface type, or where the displayed data comes from. There are
- several interface types:
-
- ``DUMMY``
- There is no data (no users, no sessions).
-
- ``NATIVE``
- The data is gathered from the system.
-
- ``SCENARIO``
- The data is gathered from a scenario (see :ref:`scenario` below).
-
- ``LIVE``
- The data is gathered from actions given in an IPC endpoint (see
- :ref:`live`).
-
- By default, the interface type is ``NATIVE``.
-
-If the interface type is ``SCENARIO``, then the following variable is
-required:
-
-``FINGER_SCENARIO``
- The scenario path (see :ref:`actions` for the format).
-
-``FINGER_INCOMING``
- The finger live action source (see :ref:`live`).
-
-The finger server should be available through the TCP port 79, which can only
-be opened by ``root`` on UNIX-like machines. Instead of using this port
-directly, which forces the use of the ``root`` user to manage the service,
-you can redirect the incoming transmissions from an interface on TCP port 79
-to another TCP port which you can open as a user (port number over 1024)
-by appending a rule in ``iptables``:
-
-.. code-block:: sh
-
- iptables -t nat -A OUTPUT -p tcp [-s <source ip>] [-d <destination ip>] \
- --dport 79 -j DNAT --to '<ip:port>'
-
-Where ``<source ip>`` is the source IP address or network that are redirected,
-``<destination ip>`` is the destination IP address or network for which the
-requests are redirected and ``<ip:port>`` is the IP and port
-you want to forward the packets to, e.g. ``127.0.0.1:4000``.
-
-For example, for running the server locally on port 3999 and only accepting
-requests from the same machine (on IPv4 and IPv6):
-
-.. code-block:: sh
-
- iptables -t nat -A OUTPUT -p tcp -s 127.0.0.1 -d 127.0.0.1 \
- --dport 79 -j DNAT --to 127.0.0.1:3999
- ip6tables -t nat -A OUTPUT -p tcp -s ::1 -d ::1 \
- --dport 79 -j DNAT --to '[::1]:3999'
-
-.. _actions:
-
-Actions
--------
-
-While interfaces provide still information to the server, some use actions
-underneath. In this section, the actions themselves are described.
-
-Flow-related actions
-~~~~~~~~~~~~~~~~~~~~
-
-The following are related to the action flow:
-
-``interrupt``
- The server freezes on the latest situation.
-
-``stop``
- The server stops on the event.
-
-All the actions after the time of any of these will be ignored.
-
-User-related actions
-~~~~~~~~~~~~~~~~~~~~
-
-User-related actions' types can be of the following:
-
-``create``
- A user has been created.
-
-``update``
- A user has been updated.
-
-``delete``
- A user has been deleted.
-
-As all of these actions are about users, they all take an additional
-``login`` argument which is the affected user's name, e.g. ``rinehart``.
-
-The ``create`` and ``update`` event takes some more arguments:
-
-``name``
- The user full name, e.g. “Mark J. Rinehart”.
-
-``shell``
- The selected login shell.
-
-``home``
- The home directory.
-
-``office``
- The user's office name, e.g. “B121 on second floor”.
-
-``plan``
- The plan path.
-
-For an ``update`` action, setting properties to ``false`` will erase their
-previous value without setting a new one.
-
-Session-related actions
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Session-related actions' types can be of the following:
-
-``login``
- A user has logged in and is active.
-
-``logout``
- A user has logged out.
-
-``idle``
- A user is now idle (not typing on the keyboard anymore).
-
-``active``
- A user is now active (typing every now and then).
-
-These actions take an additional argument ``login`` which is the user to
-which the session belongs, and an optional other ``session`` argument to
-identify the session for which the event is in the case of multiple sessions
-for the user.
-
-The ``login`` operation takes the information about the originating shell:
-
-``line``
- The physical line on which the user is connected.
-
-``host``
- The remote host from which the physical line is opened, if any.
-
-.. _scenario:
-
-Scenario (scripted actions)
----------------------------
-
-Scenarios, represented by the ``SCENARIO`` server type, are scripted
-sequences of actions.
-
-Scenarios use a TOML document describing actions, which are points in time
-where something happens. Every action has a time offset, using a TOML
-array section (``[[something]]``), and properties describing what's
-happening. Time offsets are represented the following way:
-
-::
-
- -?(<weeks>w)?(<days>[jd])?(<hours>h)?(<minutes>m)?(<seconds>s)?
-
-Where negative times, starting with a dash (``-``), are the initial situation,
-what is supposed to have happened before the beginning.
-
-For example, ``-1w5d2h`` means “1 week, 5 days and 2 hours before the
-origin” and ``2j`` means “2 days after the origin”. So if we want to make
-an action that takes place 5 seconds after the origin, the first line of the
-action will be the following one:
-
-.. code-block::
-
- [[5s]]
-
-All actions have a type represented by the ``type`` property, and other
-properties depending on the type. Types and related properties are
-described in the :ref:`actions` section.
-
-.. _live:
-
-Live actions
-------------
-
-TODO
diff --git a/docs/api/core.rst b/docs/api/core.rst
new file mode 100644
index 0000000..1fea63c
--- /dev/null
+++ b/docs/api/core.rst
@@ -0,0 +1,54 @@
+Core classes
+============
+
+.. py:module:: fingerd.core
+
+These classes constitute the core of the fingerd module.
+
+Base representations
+--------------------
+
+The following classes are objects used by other classes to represent users and
+sessions.
+
+.. autoclass:: FingerUser
+ :members: login, name, last_login, home, shell, office, plan,
+ sessions
+
+.. autoclass:: FingerSession
+ :members: start, line, host, idle
+
+The base interface class
+------------------------
+
+The following class is subclassed for making server interface classes.
+For more information, consult :ref:`discuss-interfaces`.
+
+.. autoclass:: FingerInterface
+ :members: transmit_query, search_users
+
+The base formatter class
+------------------------
+
+The following class is subclassed for making formatter classes.
+For more information, consult :ref:`discuss-formatters`.
+
+.. autoclass:: FingerFormatter
+ :members: format_query_error, format_short, format_long,
+ _format_header, _format_footer
+
+The base logger class
+---------------------
+
+The following class is subclassed for making logger classes.
+For more information, consult :ref:`discuss-loggers`.
+
+.. autoclass:: FingerLogger
+ :members: start, stop, no_query, bad_query, could_not_answer,
+ transmit_list, transmit, search_users, list
+
+The server object
+-----------------
+
+.. autoclass:: FingerServer
+ :members: start, stop, serve_forever
diff --git a/docs/cli.rst b/docs/cli.rst
new file mode 100644
index 0000000..caa70a8
--- /dev/null
+++ b/docs/cli.rst
@@ -0,0 +1,110 @@
+.. _cli:
+
+Configuration options
+=====================
+
+When run through CLI, fingerd exposes a number of configuration options using
+its own code. Each configuration option is configurable through three
+cumulative means:
+
+ * A UNIX-style command-line option.
+ * An environment variable set directly.
+ * An environment variable set through a dotenv file (``.env``).
+
+When a configuration option is given through multiple means, the resolution
+order of such the value is the one given above.
+
+Binding
+-------
+
+As for all network servers, fingerd will bind one or more network ports and
+answer to queries from all of the bound ports. These network ports are
+defined through the following option:
+
+ * The ``-b`` or ``--binds`` CLI option.
+ * The ``BIND`` or ``BINDS`` environment variable.
+
+This option must be defined one way or another, and must contain the list
+of ports to bind separated with commas (``,``). Each port must have one of
+the following formats:
+
+``example.com``
+ Bind on all addresses that ``example.com`` resolve as (IPv4 and IPv6),
+ port 79.
+
+``example.com:1234``
+ Bind on all addresses that ``example.com`` resolve as (IPv4 and IPv6),
+ port 1234.
+
+``1.2.3.4`` or ``[1.2.3.4]``
+ Bind on ``1.2.3.4`` (IPv4), port 79.
+
+``1.2.3.4:1234`` or ``[1.2.3.4]:1234``
+ Bind on ``1.2.3.4`` (IPv4), port 1234.
+
+``::1:2:3:4`` or ``[::1:2:3:4]``
+ Bind on ``::1:2:3:4`` (IPv6) port 79.
+
+``[::1:2:3:4]:1234``
+ Bind on ``::1:2:3:4`` (IPv6) port 1234.
+
+Here are some examples:
+
+.. code-block:: bash
+
+ # On modern platforms, binds on 127.0.0.1:79 (IPv4)
+ # and [::1]:79 (IPv6).
+ BIND=localhost
+
+ # Binds on 1.2.3.4:3331 and [2001:41d0:302:2200::3b2]:79.
+ BIND="1.2.3.4:3331,[2001:41d0:302:2200::3b2]:79"
+ BIND="1.2.3.4:3331,2001:41d0:302:2200::3b2"
+
+Setting the hostname
+--------------------
+
+The fingerd server answers queries with a given hostname to identify itself.
+This hostname can be configured through the following option:
+
+ * The ``-H`` or ``--hostname`` CLI option.
+ * The ``FINGER_HOST`` environment variable.
+
+It is optional and defined to ``LOCALHOST`` by default.
+
+Defining the server type
+------------------------
+
+For answering queries, the information provided by the server can come from
+different sources. The server type represents the source of the information,
+and can be configured through the following option:
+
+ * The ``-t`` or ``--type`` CLI option.
+ * The ``FINGER_TYPE`` environment variable.
+
+The possible values for this option are the following:
+
+``DUMMY``
+ There is no data (no users, no sessions).
+
+``NATIVE``
+ The data is gathered from the system.
+
+``SCENARIO``
+ The data is gathered from a scenario (see :ref:`scenario` below).
+
+By default, the interface type is ``NATIVE``. If an invalid server type is
+provided, the server is considered a dummy server.
+
+Defining the scenario
+---------------------
+
+.. todo::
+
+ Link to the format description for the scenario format?
+
+When the server is a scenario server, the path to a scenario in the TOML
+format must be provided. This path must then be configured through the
+following option:
+
+ * The ``-s`` or ``--scenario`` CLI option.
+ * The ``FINGER_ACTIONS`` environment variable.
diff --git a/docs/conf.py b/docs/conf.py
index fcbd235..9f30bd2 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -6,16 +6,15 @@
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
+import os, sys
+
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('..'))
# -- Project information -----------------------------------------------------
@@ -45,7 +44,9 @@ release = _get_release()
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
- 'sphinx.ext.autodoc'
+ 'sphinx.ext.autodoc',
+ 'sphinx_autodoc_typehints',
+ 'sphinx.ext.todo'
]
# Add any paths that contain templates here, relative to this directory.
@@ -70,11 +71,13 @@ language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.goutput*', '*.kate-swp']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
+todo_include_todos = True
+
# -- Options for HTML output -------------------------------------------------
diff --git a/docs/discuss.rst b/docs/discuss.rst
new file mode 100644
index 0000000..513b249
--- /dev/null
+++ b/docs/discuss.rst
@@ -0,0 +1,11 @@
+Discussion topics
+=================
+
+fingerd has a number of concepts necessary for a full understanding of its
+conception and an efficient use of it. You can find these concepts and
+discussion topics in the following sections.
+
+.. toctree::
+
+ discuss/fingerd-structure
+ discuss/fictional-interfaces
diff --git a/docs/discuss/fictional-interfaces.rst b/docs/discuss/fictional-interfaces.rst
new file mode 100644
index 0000000..0113ea1
--- /dev/null
+++ b/docs/discuss/fictional-interfaces.rst
@@ -0,0 +1,132 @@
+.. _fictional-interfaces:
+
+Fictional interfaces
+====================
+
+One of the ambitions for fingerd was to be able to run scenarios for inclusion
+in alternate-reality games (ARG).
+
+.. todo::
+
+ Describe live actions as well?
+
+.. _actions:
+
+Scenario
+--------
+
+While interfaces provide still information to the server, some use actions
+underneath. In this section, the actions themselves are described.
+
+Flow-related actions
+~~~~~~~~~~~~~~~~~~~~
+
+The following are related to the action flow:
+
+``interrupt``
+ The server freezes on the latest situation.
+
+``stop``
+ The server stops on the event.
+
+All the actions after the time of any of these will be ignored.
+
+User-related actions
+~~~~~~~~~~~~~~~~~~~~
+
+User-related actions' types can be of the following:
+
+``create``
+ A user has been created.
+
+``update``
+ A user has been updated.
+
+``delete``
+ A user has been deleted.
+
+As all of these actions are about users, they all take an additional
+``login`` argument which is the affected user's name, e.g. ``rinehart``.
+
+The ``create`` and ``update`` event takes some more arguments:
+
+``name``
+ The user full name, e.g. “Mark J. Rinehart”.
+
+``shell``
+ The selected login shell.
+
+``home``
+ The home directory.
+
+``office``
+ The user's office name, e.g. “B121 on second floor”.
+
+``plan``
+ The plan path.
+
+For an ``update`` action, setting properties to ``false`` will erase their
+previous value without setting a new one.
+
+Session-related actions
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Session-related actions' types can be of the following:
+
+``login``
+ A user has logged in and is active.
+
+``logout``
+ A user has logged out.
+
+``idle``
+ A user is now idle (not typing on the keyboard anymore).
+
+``active``
+ A user is now active (typing every now and then).
+
+These actions take an additional argument ``login`` which is the user to
+which the session belongs, and an optional other ``session`` argument to
+identify the session for which the event is in the case of multiple sessions
+for the user.
+
+The ``login`` operation takes the information about the originating shell:
+
+``line``
+ The physical line on which the user is connected.
+
+``host``
+ The remote host from which the physical line is opened, if any.
+
+.. _scenario:
+
+Scenario (scripted actions)
+---------------------------
+
+Scenarios, represented by the ``SCENARIO`` server type, are scripted
+sequences of actions.
+
+Scenarios use a TOML document describing actions, which are points in time
+where something happens. Every action has a time offset, using a TOML
+array section (``[[something]]``), and properties describing what's
+happening. Time offsets are represented the following way:
+
+::
+
+ -?(<weeks>w)?(<days>[jd])?(<hours>h)?(<minutes>m)?(<seconds>s)?
+
+Where negative times, starting with a dash (``-``), are the initial situation,
+what is supposed to have happened before the beginning.
+
+For example, ``-1w5d2h`` means “1 week, 5 days and 2 hours before the
+origin” and ``2j`` means “2 days after the origin”. So if we want to make
+an action that takes place 5 seconds after the origin, the first line of the
+action will be the following one:
+
+.. code-block::
+
+ [[5s]]
+
+All actions have a type represented by the ``type`` property, and other
+properties depending on the type. Types and related properties are
+described in the :ref:`actions` section.
diff --git a/docs/discuss/fingerd-structure.rst b/docs/discuss/fingerd-structure.rst
new file mode 100644
index 0000000..f0e2e90
--- /dev/null
+++ b/docs/discuss/fingerd-structure.rst
@@ -0,0 +1,36 @@
+fingerd structure
+=================
+
+fingerd's conception is centered around the server class,
+:py:class:`fingerd.server.FingerServer`, which itself makes use of
+three other classes:
+
+ * An interface, which provides the data presented by the server to the client.
+ * A formatter, which takes data obtained by the server through its interface
+ and represents it using text.
+ * A logger, which is called by the server for each operation to log server
+ and request events.
+
+.. _discuss-interfaces:
+
+Interfaces
+----------
+
+Interfaces in fingerd are subclasses of
+:py:class:`fingerd.server.FingerInterface`.
+
+.. _discuss-formatters:
+
+Formatters
+----------
+
+Formatters in fingerd are subclasses of
+:py:class:`fingerd.server.FingerFormatter`.
+
+.. _discuss-loggers:
+
+Loggers
+-------
+
+Loggers in fingerd are subclasses of
+:py:class:`fingerd.server.FingerLogger`.
diff --git a/docs/explain.rst b/docs/explain.rst
deleted file mode 100644
index c173121..0000000
--- a/docs/explain.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-Discussion topics
-=================
-
-.. todo::
-
- Discuss finger concepts, and stuff.
diff --git a/docs/howto.rst b/docs/howto.rst
deleted file mode 100644
index 5ecb176..0000000
--- a/docs/howto.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Howtos
-======
-
-In this section, you will be able to find tutorials for solving specific
-problems using fingerd.
-
-.. toctree::
diff --git a/docs/index.rst b/docs/index.rst
index 87b7ef1..4c0795f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,8 +12,8 @@ project homepage at `fingerd.touhey.pro <https://fingerd.touhey.pro/>`_!
:maxdepth: 2
onboarding
- howto
- explain
+ discuss
+ cli
api
.. _RFC 742: https://tools.ietf.org/html/rfc742
diff --git a/docs/onboarding.rst b/docs/onboarding.rst
index 767dc47..c47ebd3 100644
--- a/docs/onboarding.rst
+++ b/docs/onboarding.rst
@@ -1,17 +1,13 @@
Onboarding
==========
-``fingerd`` can be run directly and without configuration (while taking its
-default option values) with the following command:
+You're a new user trying to figure out what you can and cannot do with
+fingerd, and you're willing to experiment? You're at the right place!
+In this section, you will be able to install, run and start tweaking
+fingerd to better suit your needs.
-.. code-block:: sh
+.. toctree::
- python3 -m fingerd <command line options>
-
-The server will run while giving native information on TCP port 79 on both
-IPv4 and IPv6 (if available). However, you can tweak this behaviour by
-configuring it.
-
-.. todo::
-
- Make a little tutorial about how to run a finger server.
+ onboarding/installing
+ onboarding/running
+ onboarding/tweaking
diff --git a/docs/onboarding/installing.rst b/docs/onboarding/installing.rst
new file mode 100644
index 0000000..4996516
--- /dev/null
+++ b/docs/onboarding/installing.rst
@@ -0,0 +1,41 @@
+Installing fingerd
+==================
+
+In order to run and tweak fingerd, you must first install it; this section
+will cover the need.
+
+Dependencies
+------------
+
+fingerd dependencies are pure Python dependencies, automatically installed
+when using a package manager such as pip:
+
+ * `python-dotenv`_, used for loading ``.env`` files when the CLI is not
+ a suitable option for configuration and the environment is harder to
+ manipulate from the program running fingerd.
+ * `toml`_, used for reading scenarios for :ref:`fiction interfaces <fictional-interfaces>`.
+ * `click`_, used for implementing the command-line interfaces.
+
+Installing fingerd using pip
+----------------------------
+
+To install fingerd, you can use pip with the following command:
+
+.. code-block:: sh
+
+ python -m pip install fingerd
+
+Some notes on this command:
+
+ * On most Linux distributions, you can directly call ``pip`` (or ``pip3``
+ on those where Python 2.x is still the default); I personnally prefer
+ to call it through Python as a module.
+ * On Linux and other UNIX-like distributions where Python 2.x is still the
+ default, when Python 3.x is installed, you must usually call it using
+ ``python3`` instead of ``python``.
+ * On Microsoft Windows, the Python executable, when added to the PATH,
+ goes by the name ``py`` instead of ``python``.
+
+.. _python-dotenv: https://pypi.org/project/python-dotenv/
+.. _toml: https://pypi.org/project/toml/
+.. _click: https://pypi.org/project/click/
diff --git a/docs/onboarding/running.rst b/docs/onboarding/running.rst
new file mode 100644
index 0000000..7d7e38c
--- /dev/null
+++ b/docs/onboarding/running.rst
@@ -0,0 +1,81 @@
+Running fingerd
+===============
+
+Once installed, you can directly run fingerd with its default options
+through the following command:
+
+.. code-block:: sh
+
+ python3 -m fingerd
+
+By default, this will run a finger server on TCP port 79 for both Internet
+protocols (IPv4 and IPv6) if available, returning native information if
+implemented or dummy information (no users) if not.
+
+Using a different port
+----------------------
+
+Due to historical reasons, on UNIX-like systems, by default, TCP ports below
+1024 are considered "privileged ports", which means a program needs to be
+run as root (uid 0) to bind that port. Although on Linux, this is configurable
+(see `ip_unprivileged_port_start`_), it is rarely done in practice.
+
+However, running a network server program in root is considered a bad security
+practice. Usually, application servers are run on a custom unprivileged port
+(usually 3000, 5000 or 8000 in my experience) and a load balancer, usually
+Apache or nginx for HTTP and HTTPS services, redirects traffic to that custom
+port.
+
+Although fingerd seems harmless, it is recommended to run it as an unprivileged
+user that only can access the required data, usually session information from
+the host in its default configuration. In order for it to still be able to
+listen to requests and answer on TCP port 79, one possibility is to attribute
+a custom port to it, such as 3999, and redirect all inbound traffic on port 79
+to that custom port using iptables. This can be done by appending a rule to
+the NAT table using a command such as the following:
+
+.. code-block:: sh
+
+ iptables -t nat -A OUTPUT -p tcp [-s <source ip>] [-d <destination ip>] --dport 79 -j DNAT --to '<ip:port>'
+
+For example, if the fingerd server is running on port 3999, we can use both
+these commands to redirect traffic to that server:
+
+.. code-block:: sh
+
+ iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport 79 -j DNAT --to 127.0.0.1:3999
+ ip6tables -t nat -A OUTPUT -p tcp -d ::1 --dport 79 -j DNAT --to '[::1]:3999'
+
+Or, if you only want to accept request from localhost, you can also make use of
+the ``-s`` option to only accept traffic from localhost:
+
+.. code-block:: sh
+
+ iptables -t nat -A OUTPUT -p tcp -s 127.0.0.1 -d 127.0.0.1 --dport 79 -j DNAT --to 127.0.0.1:3999
+ ip6tables -t nat -A OUTPUT -p tcp -s ::1 -d ::1 --dport 79 -j DNAT --to '[::1]:3999'
+
+However, you must think of listening on port 3999 on the command-line, by
+using the following command-line option:
+
+.. code-block:: sh
+
+ python3 -m fingerd -b localhost:3999
+ # OR
+ BIND=localhost:3999 python3 -m fingerd
+
+See :ref:`cli` for more information.
+
+Setting the hostname
+--------------------
+
+By default, fingerd uses the hostname ``localhost`` when answering to requests.
+Say you want the server to answer with the ``EXAMPLE.ORG`` hostname. You can
+do so using the following command-line option:
+
+.. code-block:: sh
+
+ python3 -m fingerd -H example.org
+ # OR
+ FINGER_HOST=example.org python3 -m fingerd
+
+.. _`ip_unprivileged_port_start`: https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
diff --git a/docs/onboarding/tweaking.rst b/docs/onboarding/tweaking.rst
new file mode 100644
index 0000000..cab8e12
--- /dev/null
+++ b/docs/onboarding/tweaking.rst
@@ -0,0 +1,16 @@
+Tweaking fingerd
+================
+
+In order to start tweaking fingerd using Python instead of the CLI, you
+can import utilities from the module. The minimal code for running the
+server is the following:
+
+.. code-block:: python
+
+ from fingerd import FingerServer
+
+ server = FingerServer()
+ server.serve_forever()
+
+For more information, please consult the discussion topics and API reference
+on the current documentation.
diff --git a/fingerd/__init__.py b/fingerd/__init__.py
index a43600c..29d5afd 100755
--- a/fingerd/__init__.py
+++ b/fingerd/__init__.py
@@ -18,7 +18,7 @@ from sys import stderr as _stderr
from .version import version
from .errors import *
-from .server import *
+from .core import *
from .fiction import *
# ---
diff --git a/fingerd/cli.py b/fingerd/cli.py
index be665a4..d7b5b12 100644
--- a/fingerd/cli.py
+++ b/fingerd/cli.py
@@ -13,7 +13,6 @@ from .version import version as _version
from . import (FingerNativeInterface as _FingerNativeInterface,
FingerFiction as _FingerFiction,
FingerFictionInterface as _FingerFictionInterface,
- FingerLiveInterface as _FingerLiveInterface,
FingerInterface as _FingerInterface,
FingerServer as _FingerServer)
@@ -31,9 +30,7 @@ __all__ = ['cli']
envvar = ('FINGER_TYPE',))
@_click.option('-s', '--scenario', default = 'actions.toml',
envvar = ('FINGER_ACTIONS',))
-@_click.option('-i', '--incoming', default = 'ipc:///var/run/fingerd.sock',
- envvar = ('FINGER_INCOMING',))
-def cli(binds, hostname, type, scenario, incoming):
+def cli(binds, hostname, type, scenario):
""" fingerd is a modern finger (RFC 1288) server.
Find out more at <https://fingerd.touhey.pro/>. """
@@ -47,8 +44,6 @@ def cli(binds, hostname, type, scenario, incoming):
fic.load(scenario)
iface = _FingerFictionInterface(fic)
- elif type == 'live':
- iface = _FingerLiveInterface(src)
else:
if type != 'dummy':
print("warning: unknown interface type, falling back on dummy",
diff --git a/fingerd/control/__init__.py b/fingerd/control/__init__.py
deleted file mode 100755
index ee2a493..0000000
--- a/fingerd/control/__init__.py
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/usr/bin/env python3
-#**************************************************************************
-# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr>
-# This file is part of the fingerd project, which is MIT-licensed.
-#**************************************************************************
-""" The `fingerd.control` submodule is a module for adding actions to the
- fingerd live interface. """
-
-from sys import stderr as _stderr
-from argparse import ArgumentParser as _ArgumentParser
-
-__all__ = ["run",
- "FingerLiveClient", "DELETE"]
-
-# ---
-# The special DELETE value.
-# ---
-
-class _DeleteClass:
- pass
-
-DELETE = _DeleteClass()
-
-# ---
-# The client.
-# ---
-
-class FingerControlClient:
- """ Client for sending actions to the fingerd live interface. """
-
- def __init__(self, url):
- # FIXME: open the connection.
-
- pass
-
- def create_user(self, login, name = None, home = None, shell = None,
- office = None, plan = None):
- """ Create a user. """
-
- raise NotImplementedError
-
- def edit_user(self, login, name = None, home = None, shell = None,
- office = None, plan = None):
- """ Edit properties for a user. """
-
- raise NotImplementedError
-
- def delete_user(self, login):
- """ Delete a user. """
-
- raise NotImplementedError
-
- def login(self, login, session = None, line = None, host = None):
- """ Login to a finger session. """
-
- raise NotImplementedError
-
- def idle(self, login, session = None):
- """ Make the user idle on a finger session. """
-
- raise NotImplementedError
-
- def active(self, login, session = None):
- """ Make the user active again on a finger session. """
-
- raise NotImplementedError
-
- def logout(self, login, session = None):
- """ Log a user out of a finger session. """
-
- raise NotImplementedError
-
-# ---
-# The main function for the module.
-# ---
-
-def run():
- """ Main function for the submodule. """
-
- # Non-interactive command-line interface.
-
- ap = _ArgumentParser(prog = 'fingerd-control',
- description = 'Control the fingerd daemon live interface.')
- ap.add_argument('-t', '--to', help = ('interface provided by the '
- 'daemon'), default = 'ipc:///var/run/fingerd.sock')
- usap = ap.add_subparsers(metavar = 'any command', dest = 'command',
- required = True)
- ssap = usap # until i find out how to have “menus”, sorta…
-
- cap = usap.add_parser('create_user', help = 'create a user')
- cap.add_argument('login', help = 'the user\'s login')
- cap.add_argument('-n', '--name', help = "the user's name",
- default = None)
- cap.add_argument('-H', '--home', help = "the user's home directory",
- default = None)
- cap.add_argument('-s', '--shell', help = "the user's shell",
- default = None)
- cap.add_argument('-o', '--office', help = "the user's office",
- default = None)
- cap.add_argument('-p', '--plan', help = "the user's plan",
- default = None)
-
- eap = usap.add_parser('edit_user', help = 'edit a user')
- eap.add_argument('login', help = 'the user\'s login')
- eap.add_argument('-n', '--name', help = "the user's name",
- default = None)
- eap.add_argument('-H', '--home', help = "the user's home",
- default = None)
- eap.add_argument('-s', '--shell', help = "the user's shell",
- default = None)
- eap.add_argument('-o', '--office', help = "the user's office",
- default = None)
- eap.add_argument('-p', '--plan', help = "the user's plan",
- default = None)
-
- dap = usap.add_parser('delete_user', help = 'delete a user')
- dap.add_argument('login', help = "the user's login")
-
- lap = ssap.add_parser('login', help = 'log in a user')
- lap.add_argument('login', help = "the user's login")
- lap.add_argument('session', help = "the session's identifier",
- default = None, nargs = '?')
- lap.add_argument('-l', '--line', help = "the user's physical line")
- lap.add_argument('-H', '--host', help = "the remote host of the line")
-
- gap = ssap.add_parser('logout', help = 'log out a user')
- gap.add_argument('login', help = "the user's login")
- gap.add_argument('session', help = "the session's identifier",
- default = None, nargs = '?')
-
- iap = ssap.add_parser('idle', help = "make a user go idle")
- iap.add_argument('login', help = "the user's login")
- iap.add_argument('session', help = "the session's identifier",
- default = None, nargs = '?')
-
- aap = ssap.add_parser('active', help = "make a user go active")
- aap.add_argument('login', help = "the user's login")
- aap.add_argument('session', help = "the session's identifier",
- default = None, nargs = '?')
-
- args = ap.parse_args()
- args = vars(args)
-
- # Get the client and execute the command.
-
- ua = {}
- if 'home' in args and args['home'] is not None:
- ua['home'] = args['home']
- if 'name' in args and args['name'] is not None:
- ua['name'] = args['name']
- if 'office' in args and args['office'] is not None:
- ua['office'] = args['office']
- if 'plan' in args and args['plan'] is not None:
- ua['plan'] = args['plan']
- if 'shell' in args and args['shell'] is not None:
- ua['shell'] = args['shell']
-
- sa = {}
- if 'session' in args and args['session'] is not None:
- sa['session'] = args['session']
- if 'line' in args and args['line'] is not None:
- sa['line'] = args['line']
- if 'host' in args and args['host'] is not None:
- sa['host'] = args['host']
-
- clnt = FingerControlClient(args['to'])
- if args['command'] == 'create_user':
- clnt.create_user(args['login'], **ua)
- elif args['command'] == 'edit_user':
- clnt.edit_user(args['login'], **ua)
- elif args['command'] == 'delete_user':
- clnt.delete_user(args['login'])
- elif args['command'] == 'login':
- clnt.login(args['login'], **sa)
- elif args['command'] == 'logout':
- clnt.logout(args['login'], **sa)
- elif args['command'] == 'idle':
- clnt.idle(args['login'], **sa)
- elif args['command'] == 'active':
- clnt.active(args['logout'], **sa)
-
-# End of file.
diff --git a/fingerd/control/__main__.py b/fingerd/control/__main__.py
deleted file mode 100755
index df21d37..0000000
--- a/fingerd/control/__main__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python3
-#**************************************************************************
-# Copyright (C) 2017-2019 Thomas Touhey <thomas@touhey.fr>
-# This file is part of the fingerd project, which is MIT-licensed.
-#**************************************************************************
-""" Main script of the control client module.
- Runs the client depending on the configuration content. """
-
-from . import run as _run
-
-__all__ = []
-
-if __name__ == '__main__':
- _run()
-
-# End of file.
diff --git a/fingerd/server.py b/fingerd/core.py
index 395a37a..e3bc08b 100755
--- a/fingerd/server.py
+++ b/fingerd/core.py
@@ -17,6 +17,8 @@ import socketserver as _socketserver
from enum import Enum as _Enum
from io import TextIOWrapper as _TextIOWrapper, StringIO as _StringIO
from datetime import datetime as _dt, timedelta as _td
+from typing import Optional as _Optional
+from collections.abc import Sequence as _Sequence
from .errors import (
NoBindsError as _NoBindsError,
@@ -33,11 +35,102 @@ __all__ = [
# Basic representations.
# ---
+class FingerSession:
+ """ Active session on the system for a given user.
+
+ :param time: The start time of the given session;
+ by default, the current datetime. """
+
+ __slots__ = ('_start', '_line', '_host', '_idle')
+
+ def __init__(self, *_, time: _Optional[_dt] = None):
+ self._start = _dt.now()
+ self._idle = None
+ self._line = None
+ self._host = None
+
+ self.start = time
+ self._idle = self._start
+
+ def __repr__(self):
+ p = ('start', 'idle', 'line', 'orig')
+ p = (f"{x} = {repr(getattr(self, x))}" for x in p \
+ if getattr(self, x) is not None)
+ return f"{self._class__.__name__}({', '.join(p)})"
+
+ @property
+ def start(self) -> _dt:
+ """ The timestamp at which the session has started.
+
+ Note that when set, the timezone is stripped out of the
+ provided datetime. """
+
+ return self._start
+
+ @start.setter
+ def start(self, value):
+ value = value if isinstance(value, _dt) else _dt(value)
+ value = value.replace(tzinfo = None)
+
+ self._start = value
+
+ @property
+ def idle(self) -> _dt:
+ """ The timestamp since which the user is idle on the session.
+
+ Note that when set, the timezone is stripped out of the
+ provided datetime; also, if the provided datetime is before
+ the session start timestamp, it is set to it. """
+
+ return self._idle
+
+ @idle.setter
+ def idle(self, value):
+ value = value if isinstance(value, _dt) else _dt(value)
+ value = value.replace(tzinfo = None)
+
+ if value < self._start:
+ value = self._start
+
+ self._idle = value
+
+ @property
+ def line(self) -> _Optional[str]:
+ """ The line on which the user is. """
+
+ return self._line
+
+ @line.setter
+ def line(self, value):
+ self._line = None if value is None else str(value)
+
+ @property
+ def host(self) -> _Optional[str]:
+ """ The host from which the user is connected. """
+
+ return self._host
+
+ @host.setter
+ def host(self, value):
+ self._host = None if value is None else str(value)
+
+
class FingerUser:
- """ User description for the finger interface. """
+ """ User description, returned by subclasses of
+ :py:class:`FingerInterface` and used by subclasses of
+ :py:class:`FingerFormatter`.
+
+ :param login: The login of the user.
+ :param name: The display name of the user.
+ :param home: The path to the home of the user.
+ :param shell: The path to the user's default shell. """
+
+ __slots__ = ('_login', '_name', '_home', '_shell', '_office',
+ '_plan', '_last_login', '_sessions')
- def __init__(self, *_, login = None, name = None,
- home = None, shell = None):
+ def __init__(self, *_, login: _Optional[str] = None,
+ name: _Optional[str] = None, home: _Optional[str] = None,
+ shell: _Optional[str] = None):
self._login = None
self._name = ''
self._home = None
@@ -60,74 +153,79 @@ class FingerUser:
return f"{self._class__.__name__}({', '.join(p)})"
@property
- def login(self):
- """ Login name, e.g. 'cake'. """
+ def login(self) -> _Optional[str]:
+ """ The login name of the user, e.g. 'cake' or 'gaben'. """
return self._login
@login.setter
- def login(self, value):
+ def login(self, value: _Optional[str]) -> None:
self._login = value
@property
- def name(self):
- """ Full user name, e.g. 'Jean Dupont'. """
+ def name(self) -> _Optional[str]:
+ """ The display name of the user, e.g. 'Jean Dupont'. """
return self._name
@name.setter
- def name(self, value):
+ def name(self, value: _Optional[str]) -> None:
self._name = value
@property
- def last_login(self):
- """ Last login date. """
+ def last_login(self) -> _Optional[str]:
+ """ The last login date for the user, None if not known. """
return self._last_login
@last_login.setter
- def last_login(self, value):
+ def last_login(self, value: _Optional[str]) -> None:
self._last_login = None if value is None else \
value if isinstance(value, _dt) else _dt(value)
@property
- def home(self):
- """ The user's home. """
+ def home(self) -> _Optional[str]:
+ """ The path to the user's home on the given system, None if
+ not known or defined. """
return self._home
@home.setter
- def home(self, value):
+ def home(self, value: _Optional[str]) -> None:
self._home = None if value is None else str(value)
@property
- def shell(self):
- """ The user's shell. """
+ def shell(self) -> _Optional[str]:
+ """ The path to the user's shell on the given system, None if
+ not known or defined. """
return self._shell
@shell.setter
- def shell(self, value):
+ def shell(self, value: _Optional[str]) -> None:
self._shell = None if value is None else str(value)
@property
- def office(self):
- """ The user's office. """
+ def office(self) -> _Optional[str]:
+ """ The display name of the user's office, None if not known
+ or defined. """
return self._office
@office.setter
- def office(self, value):
+ def office(self, value: _Optional[str]) -> None:
self._office = None if value is None else str(value)
@property
- def plan(self):
- """ The user's plan. """
+ def plan(self) -> _Optional[str]:
+ """ The plan of the user, usually the content of the ``.plan``
+ file in the user's home on real (and kind of obsolete) UNIX-like
+ systems. """
return self._plan
@plan.setter
- def plan(self, value):
+ def plan(self, value: _Optional[str]) -> None:
if value is None:
self._plan = None
else:
@@ -135,8 +233,8 @@ class FingerUser:
self._plan = '\n'.join(value.splitlines())
@property
- def sessions(self):
- """ Current sessions. """
+ def sessions(self) -> _Sequence[FingerSession]:
+ """ The current sessions array for the user, always defined. """
return self._sessions
@@ -144,6 +242,8 @@ class FingerUser:
class _FingerSessionManager:
""" Session manager. """
+ __slots__ = ('_sessions')
+
def __init__(self):
self._sessions = []
@@ -257,65 +357,31 @@ class _FingerSessionManager:
self._sessions.insert(0, session)
-
-class FingerSession:
- """ Session for a user. """
-
- def __init__(self, *_, time = _dt.now()):
- self._start = time if isinstance(time, _dt) else _dt(time)
- self._line = None
- self._host = None
- self._idle = self._start
-
- def __repr__(self):
- p = ('start', 'line', 'orig', 'idle')
- p = (f"{x} = {repr(getattr(self, x))}" for x in p \
- if getattr(self, x) is not None)
- return f"{self._class__.__name__}({', '.join(p)})"
-
- @property
- def start(self):
- """ The session start time. """
-
- return self._start
-
- @start.setter
- def start(self, value):
- self._start = value if isinstance(value, _dt) else _dt(value)
-
- @property
- def line(self):
- """ The line on which the user is. """
-
- return self._line
-
- @line.setter
- def line(self, value):
- self._line = None if value is None else str(value)
-
- @property
- def host(self):
- """ The host from which the user is connected. """
-
- return self._host
-
- @host.setter
- def host(self, value):
- self._host = None if value is None else str(value)
-
# ---
# Formatter base class.
# ---
class FingerFormatter:
- """ Answer formatter.
- Formats the answer following RFC 1288. """
+ """ Formatter for :py:class:`FingerServer`.
+ Provides text-formatted (as strings limited to ASCII)
+ answers for given queries with given results as objects.
+
+ This class must be subclassed by other formatters.
+ Only methods not starting with an underscore are called by
+ instances of :py:class:`FingerServer`; others are utilities
+ called by these.
+
+ Unless methods are overridden to have a different behaviour,
+ this formatter aims at RFC 1288 compliance. """
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}()"
# ---
# Internal formatting utilities.
# ---
- def _format_idle(self, idle):
+ def _format_idle(self, idle: _dt) -> str:
""" Format an idle time delta. """
def _iter_idle(idle):
@@ -335,7 +401,7 @@ class FingerFormatter:
return f"{' '.join(_iter_idle(idle))} idle"
- def _format_time(self, d):
+ def _format_time(self, d: _dt) -> str:
""" Format a date and time. """
if d < _td():
@@ -352,37 +418,56 @@ class FingerFormatter:
return ""
- def _format_when(self, d):
+ def _format_when(self, d: _dt) -> str:
""" Format a date and time for 'when'. """
return d.strftime("%a %H:%M")
- # ---
- # Used formatting functions.
- # ---
-
- def format_query_error(self, name):
- """ There is an error in your query! """
+ def _format_header(self, hostname: str, raw_query: str) -> str:
+ """ Returns the header of the formatted answer for every request,
+ except when an error has occurred in the user's query.
- return "Site: {}\r\n" \
- "You have made a mistake in your query!\r\n".format(name)
+ :param hostname: The hostname configured for the server.
+ :param raw_query: The raw query given by the user.
+ :return: The header of the formatted answer as text. """
- def format_header(self, name, request):
- """ Formats the header of each request. """
+ if raw_query:
+ raw_query = ' ' + raw_query
- if request:
- request = ' ' + request
+ return f"Site: {hostname}\r\n" f"Command line:{raw_query}\r\n" "\r\n"
- return f"Site: {name}\r\n" f"Command line:{request}\r\n" "\r\n"
+ def _format_footer(self) -> str:
+ """ Returns the footer of the formatted answer for every request,
+ except when an error has occurred in the user's query.
- def format_footer(self):
- """ Formats the footer of each request (in case the developer
- wants to add something at the end, anything). """
+ :return: The footer of the formatted answer as text. """
return ""
- def format_short(self, users):
- """ Format a user list in a short fashion. """
+ # ---
+ # Used formatting functions.
+ # ---
+
+ def format_query_error(self, hostname: str, raw_query: str) -> str:
+ """ Returns the formatted answer for when an error has occurred
+ in the user's query.
+
+ :param hostname: The hostname configured for the server.
+ :param raw_query: The raw query given by the user.
+ :return: The formatted answer as text. """
+
+ return "Site: {}\r\n" \
+ "You have made a mistake in your query!\r\n".format(hostname)
+
+ def format_short(self, hostname: str, raw_query: str,
+ users: _Sequence[FingerUser]) -> str:
+ """ Returns the formatted answer for a user list in the 'short'
+ format.
+
+ :param hostname: The hostname configured for the server.
+ :param raw_query: The raw query given by the user.
+ :param users: The user list.
+ :return: The formatted answer as text. """
if not users:
return "No user list available.\r\n"
@@ -420,10 +505,20 @@ class FingerFormatter:
f"{columns[i][line][:sizes[i]]:{align[i]}{sizes[i]}}" \
for i in range(len(columns))))
- return '\r\n'.join(lines) + '\r\n'
+ return (
+ self._format_header(hostname, raw_query) +
+ '\r\n'.join(lines) + '\r\n' +
+ self._format_footer())
- def format_long(self, users):
- """ Format a user list in a long fashion. """
+ def format_long(self, hostname: str, raw_query: str,
+ users: _Sequence[FingerUser]) -> str:
+ """ Returns the formatted answer for a user list in the 'long'
+ format.
+
+ :param hostname: The hostname configured for the server.
+ :param raw_query: The raw query given by the user.
+ :param users: The user list.
+ :return: The formatted answer as text. """
if not users:
return "No user list available.\r\n"
@@ -468,20 +563,57 @@ class FingerFormatter:
res += "\r\n"
- return res
+ return (
+ self._format_header(hostname, raw_query) +
+ res +
+ self._format_footer())
# ---
# Interface (dummy) base class.
# ---
class FingerInterface:
- """ Finger interface to the users.
- Expandable (the current class defines the dummy interface). """
+ """ Data source for :py:class:`FingerServer`.
+ Provides users and answers for the various queries received
+ from the clients by the server.
+
+ This class must be subclassed by other interfaces.
+ Only methods not starting with an underscore are called by
+ instances of :py:class:`FingerServer`; others are utilities
+ called by these.
+
+ By default, it behaves like a dummy interface. """
+
+ def __repr__(self):
+ return f"{self.__class__.__name__}()"
+
+ def transmit_query(self, query: _Optional[str], host: str,
+ verbose: bool) -> str:
+ """ Transmit a user query to a foreign host, and return
+ the answer formatted by it.
+
+ If used directly (not overridden by subclasses), this
+ method will refuse to transmit finger queries.
+
+ :param query: The user query, set to None in case of
+ no query provided by the client.
+ :param host: The distant host to which to transmit the
+ query.
+ :param verbose: Whether the verbose flag (``/W``, long format)
+ has been passed by the current client
+ or not.
+ :return: The answer formatted by the distant server. """
- def transmit_query(self, user, host, verbose):
return "This server won't transmit finger queries.\r\n"
- def search_users(self, check):
+ def search_users(self, query: _Optional[str]) -> _Sequence[FingerUser]:
+ """ Search for users on the current host using the given query.
+
+ :param query: The user query, set to None in case of no
+ query provided by the client.
+ :return: The list of users found using the query provided
+ by the client. """
+
return []
# ---
@@ -489,48 +621,149 @@ class FingerInterface:
# ---
class FingerLogger:
- """ Finger logger for the server.
- Expandable, but the base class should be enough for most needs. """
+ """ Logger class for :py:class:`FingerServer`.
+ Provides methods for allowing custom behaviours on server and
+ request events.
+
+ This class must be subclassed by other loggers.
+ Only methods not starting with an underscore are called by
+ instances of :py:class:`FingerServer`; others are utilities called
+ by these.
+
+ Unless methods are overridden to have a different behaviour,
+ this logger emits messages on the given text stream, by default
+ stderr (standard error output). """
def __init__(self, stream = _sys.stderr):
self._stream = stream
self._lock = _multip.Lock()
- def _log(self, fmt, *args):
+ def __repr__(self):
+ return f"{self.__class__.__name__}()"
+
+ # ---
+ # Internal methods.
+ # ---
+
+ def _write(self, msg: str) -> None:
+ """ Write the formatted string on the given stream. """
+
self._lock.acquire()
- print('\r' + fmt.format(*args), file=self._stream)
+ print('\r' + msg, file=self._stream)
self._lock.release()
- def _logr(self, src, fmt, *args):
- self._log('[{}] ' + fmt, src, *args)
+ def _write_s(self, src: str, msg: str) -> None:
+ """ Write the formatted string and add the source address of
+ the host to the given stream using
+ :py:meth:`FingerLogger._write`. """
+
+ self._write(f'[{src}] {msg}')
+
+ # ---
+ # Public methods.
+ # ---
+
+ def start(self, host: str, port: str) -> None:
+ """ This method is called when the server starts listening on
+ the given port of the given host.
+
+ :param host: The host on which the server has started
+ listening (usually an IPv4 or IPv6 address).
+ :param port: The TCP port on which the server has started
+ listening. """
+
+ self._write(f"Starting fingerd on [{host}]:{port}.")
- def start(self, host, port):
- self._log("Starting fingerd on [{}]:{}.", host, port)
+ def stop(self, host: str, port: str) -> None:
+ """ This method is called when the server stops listening on
+ the given port of the given host.
- def stop(self, host, port):
- self._log("Stopping fingerd on [{}]:{}.", host, port)
+ :param host: The host on which the server has stopped
+ listening (usually an IPv4 or IPv6 address).
+ :param port: The TCP port on which the server has stopped
+ listening. """
- def no_query(self, source):
- self._logr(source, "no query. (possible scan)")
+ self._write(f"Stopping fingerd on [{host}]:{port}.")
- def bad_query(self, source):
- self._logr(source, "bad request.")
+ def no_query(self, source: str) -> None:
+ """ This method is called when a connection has been opened with
+ the server by a client, but immediately closed without
+ emitting a query.
- def could_not_answer(self, source):
- self._logr(source, "could not write the answer.")
+ :param source: The source address from which the client
+ has started the connection (usually an
+ IPv4 or IPv6 address). """
- def transmit_list(self, source, host):
- self._logr(source, "transmit user list query to `{}`.", host)
+ self._write_s(source, "no query. (possible scan)")
- def transmit(self, source, username, host):
- self._logr(source, "transmit user query for `{}` to `{}`.",
- username, host)
+ def bad_query(self, source: str) -> None:
+ """ This method is called when a client has emitted a bad request.
- def search_users(self, source, username):
- self._logr(source, "look for user `{}`.", username)
+ :param source: The source address from which the client
+ has started the connection (usually an
+ IPv4 or IPv6 address). """
- def list(self, source):
- self._logr(source, "list connected users.")
+ self._write_s(source, "bad request.")
+
+ def could_not_answer(self, source: str) -> None:
+ """ This method is called when a client has closed the connection
+ before or while the server was answering.
+
+ :param source: The source address from which the client
+ has started the connection (usually an
+ IPv4 or IPv6 address). """
+
+ self._write_s(source, "could not write the answer.")
+
+ def transmit_list(self, source: str, host: str):
+ """ This method is called when a client has requested the server
+ to transmit a finger request to another finger server, without
+ a user query.
+
+ :param source: The source address from which the client
+ has started the connection (usually an
+ IPv4 or IPv6 address).
+ :param host: The host to which the user wants the request
+ to be transmitted. """
+
+ self._write_s(source, f"transmit user list query to `{host}`.")
+
+ def transmit(self, source: str, username: str, host: str) -> None:
+ """ This method is called when a client has requested the server
+ to transmit a finger request to another finger server, with
+ a user query.
+
+ :param source: The source address from which the client
+ has started the connection (usually an
+ IPv4 or IPv6 address).
+ :param username: The query the user wants transmitted to
+ the provided host.
+ :param host: The host to which the user wants the request
+ to be transmitted. """
+
+ self._write_s(source, f"transmit user query for `{username}` "
+ f"to `{host}`.")
+
+ def search_users(self, source: str, username: str) -> None:
+ """ This method is called when a client has requested a user list
+ to the current host, with a query provided.
+
+ :param source: The source address from which the client
+ has started the connection (usually an
+ IPv4 or IPv6 address).
+ :param username: The provided user query. """
+
+ self._write_s(source, f"look for user `{username}`.")
+
+ def list(self, source: str) -> None:
+ """ This method is called when a client has requested a user list
+ to the current host, without any provided queries.
+
+ :param source: The source address from which the client
+ has started the connection (usually an
+ IPv4 or IPv6 address). """
+
+ self._write_s(source, "list connected users.")
# ---
# Bind-related configuration.
@@ -548,9 +781,6 @@ class _BindAddress:
""" TCP on IPv6 bind. """
TCP_IPv6 = 2
- """ IPC (Unix socket) bind. """
- IPC = 3
-
def __init__(self, family):
self._family = self.Type(family)
@@ -607,13 +837,14 @@ class _TCP6Address(_BindAddress):
class _BindsDecoder:
""" Binds decoder for fingerd.
+
Takes a raw string and the protocol name, either 'finger' (the base
protocol managed by the class) or 'fingerd-control' (the protocol
used for controlling the live fingerd interface). """
def __init__(self, raw, proto = 'finger'):
proto = proto.casefold()
- if proto not in ('finger', 'fingerd-control'):
+ if proto not in ('finger',):
raise ValueError(f"unsupported protocol {proto}")
self._binds = set()
@@ -629,13 +860,13 @@ class _BindsDecoder:
# the situation.
x = scheme
- scheme = {'finger': 'tcp', 'fingerd-control': 'ipc'}[proto]
+ scheme = {'finger': 'tcp'}[proto]
else:
# just don't add the ':' of ':/' again
x = '/' + ':/'.join(rest)
if (proto == 'finger' and scheme != 'tcp') \
- or scheme not in ('tcp', 'ipc'):
+ or scheme not in ('tcp',):
raise _InvalidBindError(addr, "unsupported scheme "
f"{repr(scheme)} for protocol {repr(proto)}")
@@ -828,7 +1059,7 @@ class _FingerTCPHandler(_socketserver.StreamRequestHandler):
query = _FingerQuery(line)
except:
self.logger.bad_query(self.src)
- ans.write(self.fmt.format_query_error(self.host))
+ ans.write(self.fmt.format_query_error(self.host, line))
return
if query is not None:
@@ -878,8 +1109,6 @@ class _FingerTCPHandler(_socketserver.StreamRequestHandler):
This method is thought to be overriden someday. """
- ans = self.fmt.format_header(self.host, query.line)
-
# Gather the content.
if query.host is not None:
@@ -902,13 +1131,12 @@ class _FingerTCPHandler(_socketserver.StreamRequestHandler):
users = self.iface.search_users(check)
if query.username or query.verbose:
- ans += self.fmt.format_long(users)
+ ans = self.fmt.format_long(self.host, query.line, users)
else:
- ans += self.fmt.format_short(users)
+ ans = self.fmt.format_short(self.host, query.line, users)
# Send the answer.
- ans += self.fmt.format_footer()
outp.write(ans)
@@ -937,11 +1165,24 @@ class _IPv6TCPServer(_socketserver.ThreadingMixIn,
class FingerServer:
- """ The Finger Server class. """
-
- def __init__(self, binds = 'localhost:79', hostname = 'LOCALHOST',
- interface = FingerInterface(), formatter = FingerFormatter(),
- logger = FingerLogger()):
+ """ The main finger server class.
+
+ :param binds: The hosts and ports on which the server should
+ listen to and answer finger requests.
+ :param hostname: The hostname to be included in answers sent
+ to clients.
+ :param interface: The interface to use for querying
+ users and sessions.
+ :param formatter: The formatter to use for formatting
+ answers sent to clients.
+ :param logger: The logger to use for logging server and
+ request events. """
+
+ def __init__(self, binds: str = 'localhost:79',
+ hostname: str = 'LOCALHOST',
+ interface: FingerInterface = FingerInterface(),
+ formatter: FingerFormatter = FingerFormatter(),
+ logger: FingerLogger = FingerLogger()):
# Check the host name.
try:
@@ -982,8 +1223,9 @@ class FingerServer:
if not self._servers:
raise _NoBindsError()
- def start(self):
- """ Bind and start the underlying servers. """
+ def start(self) -> None:
+ """ Bind all ports for the given hosts and start the underlying
+ servers in separate processes. """
def run_server(family, addr, port):
""" Run the server (will be run in another thread). """
@@ -1026,8 +1268,9 @@ class FingerServer:
args = entry[0])
entry[1].start()
- def stop(self):
- """ Stop the underlying servers. """
+ def stop(self) -> None:
+ """ Unbind all ports for the given hosts and stop the underlying
+ server processes. """
for entry in self._servers:
# Check if the thread is still here.
@@ -1043,8 +1286,11 @@ class FingerServer:
entry[1].join()
entry[1] = None
- def serve_forever(self):
- """ Serve forever. """
+ def serve_forever(self) -> None:
+ """ Shortcut for synchronously starting all servers using
+ :py:meth:`FingerServer.start`, waiting for an interrupt
+ signal, and stopping all servers using
+ :py:meth:`FingerServer.stop`. """
self.start()
diff --git a/fingerd/fiction.py b/fingerd/fiction.py
index 6d63416..d07b865 100755
--- a/fingerd/fiction.py
+++ b/fingerd/fiction.py
@@ -11,14 +11,13 @@ import copy as _copy, re as _re, math as _math
import itertools as _itertools
from datetime import datetime as _dt, timedelta as _td
-from .server import (
+from .core import (
FingerInterface as _FingerInterface,
FingerUser as _FingerUser,
FingerSession as _FingerSession)
__all__ = [
"FingerFictionInterface",
- "FingerLiveInterface",
"FingerFiction"]
_toml = None
@@ -662,17 +661,6 @@ class _FingerFictionalInterface(_FingerInterface):
else:
session.active_since = since
-class FingerLiveInterface(_FingerFictionalInterface):
- """ Fiction interface, where actions are gathered live using an
- IPC or inter-host endpoint. """
-
- def __init__(self, source, start = _dt.now()):
- super().__init__(start)
-
- # Initialize the object properties.
- # FIXME: use the `source` to get the source.
-
- raise NotImplementedError()
class FingerFictionInterface(_FingerFictionalInterface):
""" Fiction interface, to repeat a scene which the script
diff --git a/fingerd/version.py b/fingerd/version.py
index 4c7f345..c368fc7 100644
--- a/fingerd/version.py
+++ b/fingerd/version.py
@@ -5,6 +5,6 @@
#**************************************************************************
""" fingerd version definition. """
-version = "0.1"
+version = "0.2"
# End of file.
diff --git a/setup.cfg b/setup.cfg
index 4ac1313..49448f7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,7 +20,7 @@ classifiers =
[options]
zip_safe = False
include_package_data = True
-packages = fingerd, fingerd.control
+packages = fingerd
scripts =
scripts/fingerd
scripts/fingerd-control