inetd, Serverseite

Höchstwahrscheinlich kommt man nie in die Verlegenheit, einen Superserver wie inetd schreiben zu müssen. Aber es gibt viele andere Gelegenheiten, die eine ähnliche Aufgabenstellung haben. Der inetd verwaltet den kompletten Netzwerk-Krempel, damit sich die eigentliche Anwendung nur mit dem implementierten Protokoll auf Anwendungsebene rumschlagen muß. Sie liest einfach von stdin und schreibt auf stdout, und weiß im Grunde gar nichts von Netzwerken.

Diese Anforderung gibt es auch bei der Ausführung von CGI-Programmen von einem Webserver aus. Auch, wenn ein Client eine entfernte Shell bedienen soll, bietet sich dieses Vorgehen an.

Das Vorgehen ist mal wieder äußerst simpel, und zwar weil das Betriebssystem bzw. die C-Bibliothek uns die meiste Arbeit abnimmt. Unter UNIX ist fast alles wie eine Datei zu sehen - auch Sockets. Ein verbundener Socket und STDIN_FILENO bzw. STDOUT_FILENO sind sich sehr ähnlich, vor allem was die Operationen read und write betrifft. Die C-Bibliotheksfunktionen, die auf stdin und stdout zugreifen, gehen jetzt als tiefste Ebene von so einem Ding aus, sodaß sie den Unterschied gar nicht bemerken, und wie gewohnt funktionieren.

Langer Rede kurzer Sinn: man legt einfach mit dup den Socket auf die Filedeskriptoren 0, 1 und 2 für stdin, stdout und stderr, ruft mit einem exec-Befehl die eigentliche Anwendung ins Leben, fertig. Noch einen Elternprozess mit fork und wait drumherum, und wir haben einen einfachen Superserver gebaut. Je nach Anforderung kann man dann noch Nettigkeiten wie das Ändern der User-ID oder einen Aufruf von chroot einbauen.

Achtung!
Dieses Beispielprogramm reißt eine erhebliche Sicherheitslücke auf, nur auf einem Rechner ohne Internetzugang testen, INADDR_ANY anpassen, oder genau aufpassen, wann sich jemand einloggt. Funktioniert nur mit einem Client, der LF als Zeilentrenner sendet (netcat), bei CRLF (telnet) gibt's Probleme mit der Shell.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>

#define PORT    1234

void handler(int sig)
{
    int stat;
    pid_t pid;

    pid = wait(&stat);

    printf("Child %i exited.\n", pid);
}

void start_app(int sock)
{
    close(0);       /* stdin schliessen */
    close(1);       /* stdout schliessen */
    close(2);       /* stderr schliessen */
    dup(sock);      /* sock wird 0 -> stdin */
    dup(sock);      /* sock wird 1 -> stdout */
    dup(sock);      /* sock wird 2 -> stderr */

    /* man haette hier auch dup2() verwenden koennen */

    close(sock);/* sock schliessen */

    setsid();       /* neue Prozessgruppe */

    execl("/bin/sh", "sh", NULL);

    /* execl kehrt nie zurueck, wenn erfolgreich */

    perror("execl() failed");
    exit(1);
}

int main(void)
{
    int s, c;
    struct sockaddr_in addr;
    struct sigaction sa;
    pid_t pid;

    /* wichtig: sonst sind "komische" Flags gesetzt */
    memset(&sa, 0, sizeof(sa));

    s = socket(PF_INET, SOCK_STREAM, 0);
    if (s < 0)
    {
        perror("socket() failed");
        return 1;
    }

    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);

    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0)
    {
        perror("bind() failed");
        return 2;
    }

    if (listen(s, 3) < 0)
    {
        perror("listen() failed");
        return 3;
    }

    /* fuer Demonstrationszwecke wird der Prozess nicht als
     * Daemon detached, sondern bleibt an der Konsole kleben.
     */

    sa.sa_handler = handler;
    sigaction(SIGCHLD, &sa, NULL);

    for(;;)
    {
        c = accept(s, NULL, 0);
        if (c < 0)
        {
            if (errno != EINTR)
                perror("argh, dead client? accept() failed");
            continue;
        }

        switch((pid = fork()))
        {
            case -1:
                perror("fork() failed");
                break;
            case 0:
                start_app(c);
                /* kommt niemals zurueck */
                break;
            default:
                printf("Child: %i\n", pid);
                break;
        }
    }
}