Running a web server on PDP11

PDP11 was the machine just in the beginning of Networking and the Internet Rush.

As a result a maintained and security patched BSD 2.11 running pretty well still on a PDP11 machine.

If you install this custom httpd, you will be able to serve basic web requests. This is a community driven web server with basic functionality as PDP11 originally did not have webserver capability under 2.11 BSD.

There is a community driver Web Server (HTTP) Server that created for PDP use:
https://github.com/AaronJackson/2.11BSDhttpd

/* 2.11BSD httpd */
#include < sys/types.h >
#include < sys/file.h >
#include 
#include < netinet/in.h >
#include < arpa/inet.h >
#include < sys/stat.h >
#include < sys/wait.h >
#include < sys/errno.h >
#include < sys/time.h >

#include < netdb.h >
#include < signal.h >
#include < stdio.h >
#include < stdlib.h >
#include < string.h >
#include < unistd.h >

#define CGI_BIN
#define BUF_SIZE 256
#define PATH_LEN 512
#define WWW_ROOT "/var/www/www/"
#define LOGFILE "/usr/adm/httpd.log"

#define HTTP_200 "HTTP/1.1 200 OK"
#define HTTP_403 "HTTP/1.1 403 Forbidden"
#define HTTP_404 "HTTP/1.1 404 Not Found"
#define HTTP_500 "HTTP/1.1 500 Internal Server Error"

FILE *htlog;

/* Get path information and handle errors */
void chk_path(path, st)
char *path;
struct stat *st;
{
    /* stat the path. If there's an error, log it and terminate. */
    if (stat(path, st) != 0) {
        if (errno & (ENOENT | ENOTDIR | EINVAL | ENAMETOOLONG)) {
            fprintf(htlog, "404 %s\n", strerror(errno));
            printf("%s\r\n", HTTP_403);
        } else if (errno & EACCES) {
            fprintf(htlog, "403 %s\n", strerror(errno));
            printf("%s\r\n", HTTP_403);
        } else {
            fprintf(htlog, "500 %s\n", strerror(errno));
            printf("%s\r\n", HTTP_500);
        }

        fclose(htlog);
        exit(1);
    }
}

int main(argc, argv)
int argc;
char *argv[];
{
    char path[PATH_LEN];
    struct stat st;
    struct itimerval timeout;
    
    /* Open log file, quit with HTTP 500 if there's an error */
    htlog = fopen(LOGFILE, "a");
    if (!htlog) {
        printf("%s\r\n", HTTP_500);
        exit(1);
    }

    /* Log requesting host address */
    {
        struct sockaddr_in sin;
        int sval;
        struct hostent *hp;
        char *host;

        sval = sizeof(sin);
        if (getpeername(0, (struct sockaddr *)&sin, &sval) == 0) {
            /* This is a connected socket, so get the address */
            if (hp = gethostbyaddr((char *)&sin.sin_addr.s_addr,
                        sizeof(sin.sin_addr.s_addr), AF_INET))
                host = hp->h_name;
            else
                host = inet_ntoa(sin.sin_addr);
        } else {
            /* Not a socket or address otherwise unavailable */
            host = strerror(errno);
        }

        fprintf(htlog, "%s ", host);
    }

    /* Log request time */
    {
        long secs;
        char *logtime;

        time(&secs);
        logtime = ctime(&secs);
        /* Strip trailing newline from ctime string */
        logtime[strcspn(logtime, "\r\n")] = '\0';
        fprintf(htlog, "[%s] ", logtime);
    }

    /* Path starts with WWW_ROOT */
    strncpy(path, WWW_ROOT, sizeof(path));

    /* Set a timeout to terminate the process if the client is holding the
     * socket open and not completing the http request */
    timerclear(&timeout.it_interval);
    timerclear(&timeout.it_value);
    timeout.it_value.tv_sec = 60;
    /* Default action for SIGALRM is to terminate the process, so no need
     * to set up a signal handler */
    setitimer(ITIMER_REAL, &timeout, 0);

    /* Fill in path with GET/POST request */
    {
        char line[PATH_LEN];
        char *lineptr;

        while (fgets(line, sizeof(line), stdin)) {

            /* Remove trailing newline left by fgets() */
            line[strcspn(line, "\r\n")] = '\0';

            /* Detect the double line break to end req header */
            if (strlen(line) == 0)
                break;

            /* Get the path from the GET or POST request */
            if (strstr(line, "GET ") == line ||
                strstr(line, "POST ") == line) {

                fprintf(htlog, "\"%s\" ", line);

                /* Append rest of request to path */
                /* Skip request method */
                strtok(line, " ");
                /* Next token is path */
                lineptr = strtok(NULL, " ");
                if (lineptr)
                    strncat(path, lineptr, sizeof(path)-strlen(path)-1);
            }
        }
    }

    /* Cancel the timeout onw that we have the http request */
    timerclear(&timeout.it_interval);
    timerclear(&timeout.it_value);
    setitimer(ITIMER_REAL, &timeout, 0);

    /* Check for parent directories in path */
    if (strstr(path, "/..")) {
        printf("%s\r\n", HTTP_403);
        fprintf(htlog, "403 Request contains \"..\"\n");
        fclose(htlog);
        exit(1);
    }
    
    /* stat the path and handle errors */
    chk_path(path, &st);

    /* If a directory is requested, default page is index.html */
    if (st.st_mode & S_IFDIR) {
        strncat(path, "index.html", sizeof(path)-strlen(path)-1);
        /* stat and handle errors again */
        chk_path(path, &st);
    }

    /* Only serve regular files */
    if (!(st.st_mode & S_IFREG)) {
        printf("%s\r\n", HTTP_403);
        fprintf(htlog, "403 Not a regular file\n");
        fclose(htlog);
        exit(1);
    }

#ifdef CGI_BIN
    /* Check if a CGI program has been requested */
    if (strstr(path, "/cgi-bin/")) {

        int pid;
        union wait status;
        
        /* CGI program must be executable and not setuid/setgid */
        if (!(st.st_mode & S_IEXEC) ||
                (st.st_mode & (S_ISUID | S_ISGID))) {
            printf("%s\r\n", HTTP_403);
            fprintf(htlog,
                    "403 File not executable and/or is setuid/setgid\n");
            fclose(htlog);
            exit(1);
        }
        
        /* Execute CGI program */
        if (!(pid = vfork())) {
            /* Child process */
            execve(path, NULL, NULL);
            fprintf(htlog, "500 %s\n", strerror(errno));
            printf("%s\r\n", HTTP_500);
            _exit(1);

        } else {
            /* Parent process, wait for child to exit */
            wait(&status);

            if (WIFEXITED(status))
                fprintf(htlog, "Exited with status %d\n",
                        status.w_retcode);
            else if (WIFSIGNALED(status))
                fprintf(htlog, "Terminated with signal %d\n",
                        status.w_termsig);
        }

    } else
#endif /* CGI_BIN */

    /* Serve the file */
    {
        FILE *fd;
        char *ext;

        char buf[BUF_SIZE];
        int pos;

        /* Open file */
        fd = fopen(path, "r");
        if (!fd) {
            /* Earlier stat should have caught any errors, so we shouldn't
             * get here unless the file changed after the call */
            fprintf(htlog, "500 %s\n", strerror(errno));
            printf("%s\r\n", HTTP_500);
            fclose(htlog);
            exit(1);
        }

        printf("%s\r\n", HTTP_200);
        fprintf(htlog, "200 %ld\n", st.st_size);

        /* Extract file type and output content-type header */
        ext = rindex(path, '.');
        if (!ext)
            ext = "";

        if (!strcmp(ext, ".html"))
            printf("Content-Type: text/html\r\n");
        else if (!strcmp(ext, ".jpg"))
            printf("Content-Type: image/jpeg\r\n");
        else if (!strcmp(ext, ".ico"))
            printf("Content-Type: image/x-icon\r\n");
        else
            printf("Content-Type: text/plain\r\n");

        printf("Content-Length: %ld\r\n\r\n", st.st_size);
        
        while (!feof(fd) &&
                (pos = fread(buf, sizeof(*buf), sizeof(buf), fd)) > 0)
            fwrite(buf, sizeof(*buf), pos, stdout);
        
        fclose(fd);
    }

    fclose(htlog);
    return 0;
}

Having a c http server code, you will need to compile on the PDP 11 – BSD 2.11 OS:

cc httpd.c -o httpd

You will need to move the compiled web server under the BSD binary folder

mv httpd /usr/libexec/httpd

You will need to add the new web service to the inetd
/etc/inetd.conf


http stream tcp nowait nobody /usr/libexec/httpd httpd

You will need to amend the service file as well
/etc/services

http 80/tcp

You can either re-start the PDP Operating System or kill the existing inetd service and re-start manually

ps auwx | grep inetd

kill -9 <pid>

Share with: