💾 Archived View for thrig.me › tech › openbsd › fingerd.gmi captured on 2024-06-16 at 13:37:28. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-11-14)

-=-=-=-=-=-=-

fingerd

fingerd can be enabled on OpenBSD, though the default configuration is perhaps not suitable to expose to the whole internet, as this will expose the IP addresses that one logs in from. This may not be a good idea, especially if one is prone to annoy young males at their "wild age", as Stilgar puts it, or for various other reasons. It is difficult to avoid creepy corporate tracking these days, but one may not want to give them free hand-outs.

You could only login over a wireguard tunnel, in which case the IP address shown will probably be RFC 1918 something, but it's probably more sensible to expose only the information you want to the whole internet, which probably does not include the IP addresses you use to connect from.

The last step should be to open up TCP/79 in the firewall to the internet. The first step might be to ensure that TCP/79 is blocked by the firewall. Next, write a custom script for fingerd to execute. Most simply:

    $ cat /usr/local/libexec/lawn
    #!/bin/sh
    echo get off my lawn
    $ chmod +x /usr/local/libexec/lawn

And then to modify /etc/inetd.conf to execute this program:

    $ grep fingerd /etc/inetd.conf
    finger          stream  tcp     nowait  _fingerd /usr/local/libexec/fingerd   fingerd -lsmP /usr/libexec/lawn
    finger          stream  tcp6    nowait  _fingerd /usr/local/libexec/fingerd   fingerd -lsmP /usr/libexec/lawn

You may want to remove the -l flag to cut down on log noise? Once things are debugged, that is.

inetd will need to be enabled and started. This should properly be done with configuration management (Ansible or whatnot), if you are that organized.

    $ doas rcctl enable inetd
    $ doas rcctl start  inetd
    $ finger root@localhost
    [localhost/127.0.0.1]
    get off my lawn

But What About Multiple Users?

The observant may notice that our lawn script gives the same answer regardless of the user, or lack of user given:

    finger @localhost
    finger squarebobspongepants@localhost
    ...

Here the dreaded "second-system effect" comes into play, as we must bloat our elegant lawn script with features and wingdings and cover various annoying edge cases, such as someone connecting but then leaving the connection open without sending anything.

    // myfingerd - a small finger server (to be called by inetd(8))
    //
    // this program reads from a ~/.lawn file if there is one for a valid
    // user, or otherwise returns a generic message or otherwise exits with
    // a non-zero exit status word should something go awry

    #include <sys/time.h>

    #include <ctype.h>
    #include <fcntl.h>
    #include <pwd.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>

    #define MAXLEN 32 + 2 // max length of username + CRNL

    void
    handle_alarm(int sig)
    {
        exit(1);
    }

    int
    main(void)
    {
        if (pledge("stdio rpath unveil", NULL) == -1) goto NOPE;

        // fail if the request takes too long, e.g. when the remote end
        // does a `telnet example.org 79` and then walks away
        struct itimerval timeout = {0};
        timeout.it_value.tv_sec  = 5;
        if (signal(SIGALRM, handle_alarm) == SIG_ERR) goto NOPE;
        if (setitimer(ITIMER_REAL, &timeout, NULL) == -1) goto NOPE;

        char user[MAXLEN + 1];
        if (fgets(user, MAXLEN + 1, stdin) == NULL) goto NOPE;

        size_t len = strnlen(user, MAXLEN);
        if (len < 2) goto NOPE;
        if (!(user[len - 1] == '\n' && user[len - 2] == '\r')) {
            if (len == MAXLEN)
                goto ALLUSERS; // the input filled our buffer
            else
                goto NOPE; // did not terminate with CRNL
        }
        len -= 2;
        if (len == 0) goto ALLUSERS;

        user[len] = '\0';
        char *ch  = user;
        // maybe you have _ or other such in your usernames? I don't
        while (*ch != '\0') {
            if (!isalnum(*ch)) goto ALLUSERS;
            ch++;
        }

        struct passwd *pw = getpwnam(user);
        if (!pw) goto ALLUSERS;
        char *home = pw->pw_dir;
        if (!home) goto ALLUSERS;
        if (chdir(home) == -1) goto ALLUSERS;

        // traditional would be ".plan" and ".project" but those
        // might be better reserved for internal use
        if (unveil(".lawn", "r") == -1) goto NOPE;
        if (unveil(NULL, NULL) == -1) goto NOPE;
        int fd = open(".lawn", O_RDONLY | O_NOFOLLOW);
        if (fd < 0) goto ALLUSERS;
        if (pledge("stdio", NULL) == -1) goto NOPE;
        char buf[4096];
        while (1) {
            ssize_t amount = read(fd, buf, sizeof(buf));
            if (amount <= 0) break;
            write(STDOUT_FILENO, buf, (size_t) amount);
        }
        //close(fd);
        exit(0);

    ALLUSERS:
        if (pledge("stdio", NULL) == -1) goto NOPE;
        write(STDOUT_FILENO, "get off our lawns\n", 18);
        exit(0);

    NOPE:
        exit(1);
    }

myfingerd.c

    finger		stream	tcp	nowait	_fingerd /usr/local/libexec/myfingerd
    finger		stream	tcp6	nowait	_fingerd /usr/local/libexec/myfingerd

A security risk here is that an attacker might use this script to enumerate users on the system, perhaps to find people to spam, phish, check for weak passwords by way of SMTP, etc. Attackers can be terribly clever here and may be able to use timing information to determine if an account exists or not. If this is a concern, maybe delay the responses so that "no account exists" cannot be distinguished from "there was an account, but they did not have a .lawn file".

gemini://gemini.thebackupbox.net/~epoch/blog/fingering

Probably you should not run finger on the squeaky clean corporate and spammer overrun internet. But where is the fun in that?

tags #c #finger #inetd #openbsd