├── .gitignore ├── LICENSE ├── README.md ├── finger2020 ├── install.sh ├── photo.jpg └── systemd ├── finger2020.env ├── finger2020.socket └── finger2020@.service /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Michael Lazar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # finger2020 2 | 3 | *A tiny, secure [finger](https://en.wikipedia.org/wiki/Finger_protocol) daemon for single-user unix systems.* 4 | 5 | ![photo](photo.jpg) 6 | 7 | *Les Earnest, the creator of the original finger program, at SAIL circa 1976 [(source)](https://www.saildart.org/Visitor_1976/).* 8 | 9 | ## About 10 | 11 | This finger service uses files to load contact information, and will not extract 12 | any sensitive information directly from the host system. So relax! Only one user 13 | profile is supported (hence, "single-user"). It's intended to be used by a sysop 14 | to broadcast server news and/or personal contact information over the internet. 15 | 16 | ## Operation 17 | 18 | This program is designed to be hosted behind a systemd socket or a comparable 19 | ``inetd``-like service. The program will read a one-line query from stdin. The 20 | response string will be written to stdout, and all error/logging messages will 21 | be directed to stderr and should be setup to route to an appropriate system log 22 | manager. 23 | 24 | ## Query Support 25 | 26 | Only the user list and user status queries are supported. Queries with 27 | ambiguous user names will be rejected. Queries with hostnames will be rejected. 28 | The verbose flag (``/W``) is accepted but will be ignored. 29 | 30 | ## Usage 31 | 32 | ```bash 33 | # Query user list 34 | echo -e "\r\n" | finger2020 35 | 36 | # Query user information 37 | echo -e "username\r\n" | finger2020 38 | ``` 39 | 40 | ## Configuration 41 | 42 | Settings are defined through environment variables. 43 | 44 | #### ``FINGER_NAME`` 45 | 46 | A string that will represent the user's finger name. This can be any 47 | arbitrary value and does not need to correspond to a real user on the 48 | system. 49 | 50 | #### ``FINGER_CONTACT`` 51 | 52 | This file replaces the section of the query response that contains 53 | information that finger would typically pull from */etc/passwd* and the GECOS 54 | field. Common values to include in this section include login name, real name, 55 | phone number, office, address, and recent system activity. 56 | 57 | #### ``FINGER_PROJECT`` 58 | 59 | This file typically contains a multi-line "profile page" with additional 60 | information about the user. The information documented in this file does not 61 | frequently change. 62 | 63 | #### ``FINGER_PLAN`` 64 | 65 | This file typically contains a brief description of what the user is currently 66 | working on. This file is intended to be updated frequently and there are no 67 | restrictions on what type of information the user can include. 68 | 69 | #### ``FINGER_INFO_LABELS`` 70 | 71 | Set to "true" or "false" to toggle showing the 'Project:' and 'Plan:' labels 72 | in the response. 73 | 74 | ## Links 75 | 76 | - [RFC 742 - NAME/FINGER](https://tools.ietf.org/html/rfc742) 77 | - [RFC 1288 - The Finger User Information Protocol](https://tools.ietf.org/html/rfc1288) 78 | - [History of the Finger Protocol](http://www.rajivshah.com/Case_Studies/Finger/Finger.htm) 79 | - [IETF Draft finger URL Specification](https://tools.ietf.org/html/draft-ietf-uri-url-finger-02) 80 | - [Info-Gathering Tutorial](http://cd.textfiles.com/hmatrix/Tutorials/hTut_0173.html) 81 | - [Giving the Finger to port 79 / Simple Finger Deamon Tutorial](http://cd.textfiles.com/hmatrix/Tutorials/hTut_0269.html) 82 | -------------------------------------------------------------------------------- /finger2020: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | finger2020 - A tiny, secure finger daemon for single-user UNIX systems 4 | 5 | ## About 6 | 7 | This finger service uses files to load contact information, and will not extract 8 | any sensitive information directly from the host system. So relax! Only one user 9 | profile is supported (hence, "single-user"). It's intended to be used by a 10 | sysop to broadcast server news and/or personal contact information over the 11 | internet. 12 | 13 | ## Operation 14 | 15 | This program is designed to be hosted behind a systemd socket or a comparable 16 | ``inetd``-like service. The program will read a one-line query from stdin. The 17 | response string will be written to stdout, and all error/logging messages will 18 | be directed to stderr and should be setup to route to an appropriate system log 19 | manager. 20 | 21 | ## Query Support 22 | 23 | Only the user list and user status queries are supported. Queries with 24 | ambiguous user names will be rejected. Queries with hostnames will be rejected. 25 | The verbose flag (``/W``) is accepted but will be ignored. 26 | 27 | ## Usage 28 | 29 | ```bash 30 | # Query user list 31 | echo -e "\r\n" | finger2020 32 | 33 | # Query user information 34 | echo -e "username\r\n" | finger2020 35 | ``` 36 | 37 | ## Configuration 38 | 39 | FINGER_NAME 40 | A string that will represent the user's finger name. This can be any 41 | arbitrary value and does not need to correspond to a real user on the 42 | system. 43 | 44 | FINGER_CONTACT 45 | This file replaces the section of the query response that contains 46 | information that finger would typically generate. Common values to include 47 | in this section include login name, real name, phone number, office, 48 | address, and recent system activity. 49 | 50 | FINGER_PROJECT 51 | This file typically contains a multi-line "profile page" with additional 52 | information about the user. The information documented in this file does not 53 | frequently change. 54 | 55 | FINGER_PLAN 56 | 57 | This file typically contains a brief description of what the user is 58 | currently working on. This file is intended to be updated frequently and 59 | there are no restrictions on what type of information the user can include. 60 | 61 | FINGER_INFO_LABELS 62 | 63 | Set to "true" or "false" to toggle showing the 'Project:' and 'Plan:' labels 64 | in the response. 65 | """ 66 | import os 67 | import re 68 | import sys 69 | 70 | __author__ = "Michael Lazar" 71 | __copyright__ = "(c) 2020 Michael Lazar" 72 | __license__ = "The MIT License (MIT)" 73 | __title__ = "finger2020" 74 | __version__ = "1.0.0" 75 | 76 | if sys.version_info[:2] < (3, 6): 77 | raise RuntimeError("Unsupported python version, required 3.6+") 78 | 79 | HOME = os.path.expanduser("~") 80 | 81 | FINGER_NAME = os.getenv("FINGER_NAME", "anonymous") 82 | FINGER_CONTACT = os.getenv("FINGER_CONTACT", os.path.join(HOME, ".contact")) 83 | FINGER_PLAN = os.getenv("FINGER_PLAN", os.path.join(HOME, ".plan")) 84 | FINGER_PROJECT = os.getenv("FINGER_PROJECT", os.path.join(HOME, ".project")) 85 | FINGER_INFO_LABELS = os.getenv("FINGER_INFO_LABELS", "true") 86 | 87 | # Query specification: 88 | # {Q1} ::= [{W}|{W}{S}{U}]{C} 89 | # {Q2} ::= [{W}{S}][{U}]{H}{C} 90 | # {U} ::= username 91 | # {H} ::= @hostname | @hostname{H} 92 | # {W} ::= /W 93 | # {S} ::= | {S} 94 | # {C} ::= 95 | # 96 | # Note: I suspect the definition for {Q1} is incomplete because the examples 97 | # show querying for a {U} user without the {W} token. My interpretation of 98 | # the intended implementation is [ {W} | {U} | {W}{S}{U} ] {C}. 99 | # Note: The links browser appears to break spec and send "/W \r\n". 100 | tokens = { 101 | "U": "(?P\\w+)", 102 | "H": "(?P@[\\w@.]+)", 103 | "W": "(/W)", # Verbose mode, this flag is ignored 104 | "S": "( +)", 105 | "C": "(\r?\n)", # Break spec and make the carriage return optional 106 | } 107 | q1_list_re = re.compile("({W}{S}?)?{C}".format(**tokens)) 108 | q1_search_re = re.compile("({W}{S})?{U}{C}".format(**tokens)) 109 | q2_re = re.compile("({W}{S})?{U}?{H}{C}".format(**tokens)) 110 | 111 | 112 | def read_file(filename): 113 | try: 114 | with open(filename) as fp: 115 | # Normalize file line endings to 116 | text = "\r\n".join(line.rstrip() for line in fp.readlines()) 117 | except OSError: 118 | text = "" 119 | return text 120 | 121 | 122 | def render_user_info(): 123 | contact = read_file(FINGER_CONTACT) 124 | project = read_file(FINGER_PROJECT) 125 | plan = read_file(FINGER_PLAN).strip() 126 | if FINGER_INFO_LABELS == "false": 127 | return "\r\n".join([contact, project, plan]) 128 | else: 129 | return "\r\n".join([contact, f"Project:", project, "", f"Plan: {plan}"]) 130 | 131 | 132 | def handle(query): 133 | # We don't need no stinkin' := walrus operator! 134 | match = q2_re.fullmatch(query) 135 | if match: 136 | return "Finger forwarding service denied" 137 | 138 | match = q1_search_re.fullmatch(query) 139 | if match: 140 | if match.group("username") == FINGER_NAME: 141 | return render_user_info() 142 | else: 143 | return "Finger user not found" 144 | 145 | match = q1_list_re.fullmatch(query) 146 | if match: 147 | return f"There is only one user on this server: {FINGER_NAME}" 148 | 149 | return "Finger invalid query" 150 | 151 | 152 | def main(): 153 | query = sys.stdin.readline(1024) 154 | sys.stderr.write(f"Received query: {query!r}\n") 155 | response = handle(query) 156 | sys.stdout.write(response) 157 | sys.stdout.write("\r\n") 158 | 159 | 160 | if __name__ == "__main__": 161 | main() 162 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Installing /usr/bin/finger2020" 4 | install finger2020 /usr/bin/finger2020 5 | 6 | echo "Installing systemd files..." 7 | install systemd/finger2020.socket /etc/systemd/system/finger2020.socket 8 | install systemd/finger2020@.service /etc/systemd/system/finger2020@.service 9 | install -b systemd/finger2020.env /etc/sysconfig/finger2020.env 10 | 11 | echo "Enabling systemd service..." 12 | systemctl daemon-reload 13 | systemctl enable finger2020.socket 14 | systemctl start finger2020.socket 15 | systemctl status finger2020.socket 16 | 17 | echo "Done!" 18 | -------------------------------------------------------------------------------- /photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michael-lazar/finger2020/2f74eeee091a615e92f12cd7c73a234d4462a427/photo.jpg -------------------------------------------------------------------------------- /systemd/finger2020.env: -------------------------------------------------------------------------------- 1 | # /etc/sysconfig/finger2020.env 2 | 3 | # FINGER_NAME="mozz" 4 | # FINGER_CONTACT="/etc/finger/contact.txt" 5 | # FINGER_PROJECT="/etc/finger/project.txt" 6 | # FINGER_PLAN="/etc/finger/plan.txt" 7 | # FINGER_INFO_LABELS="true" 8 | -------------------------------------------------------------------------------- /systemd/finger2020.socket: -------------------------------------------------------------------------------- 1 | # /etc/systemd/system/finger2020.socket 2 | [Unit] 3 | Description=Finger Server 4 | 5 | [Socket] 6 | ListenStream=79 7 | Accept=yes 8 | 9 | [Install] 10 | WantedBy=sockets.target -------------------------------------------------------------------------------- /systemd/finger2020@.service: -------------------------------------------------------------------------------- 1 | # /etc/systemd/system/finger2020@.service 2 | [Unit] 3 | Description=Finger Server 4 | Requires=finger2020.socket 5 | 6 | [Service] 7 | Type=simple 8 | ExecStart=/usr/bin/finger2020 9 | EnvironmentFile=/etc/sysconfig/finger2020.env 10 | StandardInput=socket 11 | StandardError=journal 12 | 13 | [Install] 14 | WantedBy=multi-user.target --------------------------------------------------------------------------------