Help save the worlds first webserver. We need to track down a copy of the original tarball WWWDaemon_0.1.tar.Z to preserve in the annals of history. [x-post] : compsci
1991年8月20日、Tim Berners-Leeはcomp.sys.next.announceに対して、歴史に残る告知を投稿した。「info.cern.chから、WorldWideWebアプリケーションのアルファ版リリースをソースコードとバイナリの両方で公開する」
この投稿は、以下の三つのソフトウェアを示し、我々が今知る、いわゆるインターネッツを開始したのだ。
/pub/WWWNeXTStepEditor_0.12.tar.Z NeXT application + sources
/pub/WWWLineMode_0.11.tar.Z Portable Line Mode Browser
/pub/WWWDaemon_0.1.tar.Z Simple server
エディターとブラウザーは、多くのサイトで保存されている。例えばここだ。しかし、最初のWebサーバーは、どこにも見当たらない。これは憂慮すべき事態である。公開アクセスできるFPSサーバーを大量に漁った末、容易に入手できる中でもっとも初期の版は、v 2.08bである(apacheとw3.rogの両方の公開FTPにコピーが置いてある)。しかし、これ以前のものは見当たらない。
Redditターよ。汝ら猫を救い、政府と戦い、正義を守り続けてきた(あと他にも下らないことたくさんしてるけどな)。この歴史的に重要な財産を守り、もって次の世代に伝えよ。
追記:Henrik Frystyk Nielsen、WWWの最初のコントリビューターも参加した。
https://twitter.com/#!/frystyk/status/173128770763501568
追記2:Timのw3.orgのアドレスにメール送ってみた。でも多分、「インターネット早くしてよ!」ってなメールに埋もれてしまうだろうけど。
これは非常に興味深い問題である。もし歴史家が、インターネットの歴史について研究する場合、当然、最初のWebサーバーのソースコードの改良具合を順々に検証できる必要がある。しかるに、たったの21年しか立っていない今、その最初のオリジナルのソースコードが見つからないとは脅威だ。堂々と公開されていたはずなのに存在しないのだ。いわんや著作権とDRMで保存を妨害しているコンテンツをや。
そろそろ、我々は将来のために、文化的財産を保存する方法を真剣に考えるべきだ。さもなければ、将来の歴史家は海賊たちに感謝することになる。
参考:本の虫: なぜ歴史には海賊が必要なのか
追記:なんと見つかったそうだ。しかし、utilities.hがないのでコンパイルできないそうだ。どうもコメントから察するに、何らかのマクロを定義したヘッダのようだが。実際にコンパイルしていないので、どこで引っかかるのかわからないが、コードからどんなマクロなのか推測できそうなものである。
/* TCP/IP based server for HyperText TCPServer.c
** ---------------------------------
**
** History:
** 2 Oct 90 Written TBL. Include filenames for VM from RTB.
*/
/* Module parameters:
** -----------------
**
** These may be undefined and redefined by syspec.h
*/
#define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP)*/
#define MAX_CHANNELS 20 /* Number of channels we will keep open */
#define BUFFER_SIZE 4096 /* Arbitrary size for efficiency */
#define WILDCARD '*' /* Wildcard used in addressing */
#define NETCLOSE close /* Routine to close a TCP-IP socket */
#define NETREAD read /* Routine to read from a TCP-IP socket */
#define NETWRITE write /* Routine to write to a TCP-IP socket */
#define FIRST_TCP_PORT 5000 /* When using dynamic allocation */
#define LAST_TCP_PORT 5999
#ifdef NeXT
#define SELECT /* Handle >1 channel if we can. */
#include <libc.h> /* NeXT has all this packaged up */
#define ntohs(x) (x)
#define htons(x) (x)
#include <streams/streams.h>
#else
#include <stdio.h>
/* VM doesn't have a built-in predefined token, so we cheat: */
#ifdef __STDIO__
#define VM
#else
#include <string.h> /* For bzero etc - not NeXT or VM */
#endif
#define SELECT /* Handle >1 channel if we can. */
#endif
#ifndef VM
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> /* Must be after netinet/in.h */
#include <netdb.h>
#include <errno.h> /* independent */
#include <sys/time.h> /* independent */
#define TOASCII(c) (c)
#define FROMASCII(c) (c)
#else /* VM */
#include <manifest.h>
#include <bsdtypes.h>
#include <stdefs.h>
#include <socket.h>
#include <in.h>
#include <netdb.h>
#include <errno.h> /* independent */
extern char asciitoebcdic[], ebcdictoascii[];
#define TOASCII(c) (c=='\n' ? 10 : ebcdictoascii[c])
#define FROMASCII(c) (c== 10 ? '\n' : asciitoebcdic[c])
#include <bsdtime.h>
#define index(a,b) my_index(a,b) /* Why index() doesn't work I don't know */
#endif
#include "utilities.h" /* Coding convention macros */
/* Default macros for manipulating masks for select()
*/
#ifndef FD_SET
typedef unsigned int fd_set;
#define FD_SET(fd,pmask) (*(pmask)) |= (1<<(fd))
#define FD_CLR(fd,pmask) (*(pmask)) &= ~(1<<(fd))
#define FD_ZERO(pmask) (*(pmask))=0
#define FD_ISSET(fd,pmask) (*(pmask) & (1<<(fd)))
#endif
/* Module-Global Variables
** -----------------------
*/
PRIVATE enum role_enum {master, slave, transient, passive} role;
PRIVATE struct sockaddr_in soc_address; /* See netinet/in.h */
PRIVATE int soc_addrlen;
PRIVATE int master_soc; /* inet socket number to listen on */
PRIVATE int com_soc; /* inet socket number to read on */
PRIVATE fd_set open_sockets; /* Mask of channels which are active */
PRIVATE int num_sockets; /* Number of sockets to scan */
PRIVATE BOOLEAN dynamic_allocation; /* Search for port? */
PRIVATE char buffer[BUFFER_SIZE];
/* Find character within string
**
** See the ANSI definition of index()
*/
#ifdef VM
char * my_index(char *s, char c)
{
char *p;
for(p=s; *p && (*p!=c); p++) /*scan */ ;
return *p ? p : 0;
}
#endif
/* Read a file
** -----------
**
** The function reads a file from the given file POINTER
** into the given file DESCRIPTOR!
**
** On exit,
** returns 0 for end of file, <0 for error.
*/
PRIVATE int read_file(int dest, FILE * source)
{
unsigned char * status;
for(;;) {
status = fgets(buffer, BUFFER_SIZE, source);
if (!status) return -1;
(void) write(dest, buffer, status);
}
}
/* Strip white space off a string
**
** On exit,
** Return value points to first non-white character, or to 0 if none.
** All trailing white space is overwritten with zero.
*/
PRIVATE char * strip(char * s)
{
#define SPACE(c) ((c==' ')||(c=='\t')||(c=='\n'))
char * p=s;
for(p=s;*p;p++); /* Find end of string */
for(p--;p>=s;p--) {
if(SPACE(*p)) *p=0; /* Zap trailing blanks */
else break;
}
while(SPACE(*s))s++; /* Strip leading blanks */
return s;
}
/* Send a string down a socket
** ---------------------------
**
** The trailing zero is not sent.
** There is a maximum string length.
*/
PRIVATE int write_ascii(int soc, char * s)
{
char * p;
char ascii[255];
char *q = ascii;
for (p=s; *p; p++) {
*q++ = TOASCII(*p);
}
return write(soc, ascii, p-s);
}
/* Handle one message
** ------------------
**
** On entry,
** soc A file descriptor for input and output.
** On exit,
** returns >0 Channel is still open.
** 0 End of file was found, please close file
** <0 Error found, please close file.
*/
int handle(int soc)
{
#define COMMAND_SIZE 255
#define MAX_LINES 10000 /* Maximum number of lines returned */
char command[COMMAND_SIZE+1];
int status;
char * arg; /* Pointer to the argument string */
char * filename; /* Pointer to filename or group list*/
char * keywords; /* pointer to keywords */
char system_command[COMMAND_SIZE+1];
int lines, i;
int fd; /* File descriptor number */
for (;;) {
status = read(soc, command, COMMAND_SIZE);
if (status<=0) {
if (TRACE) printf("read returned %i, errno=%i\n", status, errno);
return status; /* Return and close file */
}
command[status]=0; /* terminate the string */
#ifdef VM
{
char * p;
for (p=command; *p; p++) {
*p = FROMASCII(*p);
if (*p == '\n') *p = ' ';
}
}
#endif
if (TRACE) printf("read string is `%s'\n", command);
arg=index(command,' ');
if (arg) {
*arg++ = 0; /* Terminate command & move on */
arg = strip(arg); /* Strip leading & trailing space */
if (0==strcmp("GET", command)) { /* Get a file */
/* Remove host and any punctuation. (Could use HTParse to remove access too @)
*/
filename = arg;
if (arg[0]=='/') {
if (arg[1]=='/') {
filename = index(arg+2, '/'); /* Skip //host/ */
if (!filename) filename=arg+strlen(arg);
} else {
filename = arg+1; /* Assume root: skip slash */
}
}
if (*filename) { /* (else return test message) */
keywords=index(filename, '?');
if (keywords) *keywords++=0; /* Split into two */
#ifdef VM
/* Formats supported by FIND are:
**
** /FIND Help for find general index
** /FIND/ (same)
** /FIND/? (same)
** /FIND/group.disk.ft.fn Get a document from a disk
** /FIND/grouplist? Help for group index
** /FIND/grouplist?keylist Search a set of disks for keywords
** where grouplist = group+group+group.. or void
** keylist = key+key+key... (not void)
*/
if((0==strncmp(filename,"FIND",4)) &&
((filename[4]==0) || (filename[4]=='/') || (filename[4]='?'))) {
filename=filename+4;
if (*filename=='/') filename++; /* Skip first slash */
if (!*filename) { /* If no data at all, */
if (!keywords)
keywords = filename; /* Respond as if just "?" given */
}
if (keywords) {
char *p;
while((p=index(filename,'+'))!=0) *p=' ';/* Remove +s */
while((p=index(keywords,'+'))!=0) *p=' ';/* Remove +s */
if (!*keywords) { /* If no keywords */
write_ascii(soc,
"<IsIndex><TITLE>Keyword Index</TITLE>\n");
if (*filename) {
write_ascii(soc, "This index covers groups: `");
write_ascii(soc, filename);
} else {
write_ascii(soc,
"This is the general CERN public document index");
}
write_ascii(soc,
".<P>Use a keyword search to get documents, help files, etc.<P>\n");
return 0;
}
strcpy(system_command,"EXEC NICFIND1 ");
strcat(system_command, keywords);
if (*filename) {
strcat(system_command, " ( ");
strcat(system_command, filename);
}
} else {
strcpy(system_command,"EXEC NICFOUN1 ");
strcat(system_command, filename);
}
lines = system(system_command);
if (TRACE) printf("Command `%s' returned %i lines.\n",
system_command, lines);
if (lines<=0) {
write_ascii(soc,
"\nSorry, the FIND server could not execute\n `");
write_ascii(soc, system_command);
write_ascii(soc, "'\n\n");
return 0;
}
/* Copy the file across: */
for(;lines; lines--){ /* Speed necessary here */
char *p;
fgets(buffer, BUFFER_SIZE, stdin);
/* if (strcmp(buffer,"<END_OF_FILE>")==0) break; */
for(p=buffer; *p; p++) *p = TOASCII(*p);
write(soc, buffer, p-buffer);
}
return 0;
} /* if FIND */
#else
fd = open(arg, O_RDONLY, 0 ); /* un*x open @@ */
if (fd<0) return fd;
status = read_file(soc, fd);
close(fd);
return 0;
#endif
}
}
}
write_ascii(soc, "<h1>Small network access test v1.01</h1>\n");
write_ascii(soc, "\n\nThe (unrecognised) command used was `");
write_ascii(soc, command);
write_ascii(soc, "'.\n\n");
if (TRACE) printf("Return test string written back'\n");
return 0; /* End of file - please close socket */
} /* for */
} /* handle */
/*_____________________________________________________________________________________
*/
/* Encode INET status (as in sys/errno.h) inet_status()
** ------------------
**
** On entry,
** where gives a description of what caused the error
** global errno gives the error number in the unix way.
**
** On return,
** returns a negative status in the unix way.
*/
#ifdef vms
extern int uerrno; /* Deposit of error info (as perr errno.h) */
extern int vmserrno; /* Deposit of VMS error info */
extern volatile noshare int errno; /* noshare to avoid PSECT conflict */
#else
#ifndef errno
extern int errno;
#endif
#endif
PRIVATE int inet_status(where)
char *where;
{
CTRACE(tfp, "TCP: Error %d in `errno' after call to %s() failed.\n",
errno, where);
#ifdef vms
CTRACE(tfp, " Unix error number (uerrno) = %ld dec\n", uerrno);
CTRACE(tfp, " VMS error (vmserrno) = %lx hex\n", vmserrno);
#endif
return -errno;
}
/* Parse a cardinal value parse_cardinal()
** ----------------------
**
** On entry,
** *pp points to first character to be interpreted, terminated by
** non 0:9 character.
** *pstatus points to status already valid
** maxvalue gives the largest allowable value.
**
** On exit,
** *pp points to first unread character
** *pstatus points to status updated iff bad
*/
PRIVATE unsigned int parse_cardinal(pstatus, pp, max_value)
int *pstatus;
char **pp;
unsigned int max_value;
{
int n;
if ( (**pp<'0') || (**pp>'9')) { /* Null string is error */
*pstatus = -3; /* No number where one expeceted */
return 0;
}
n=0;
while ((**pp>='0') && (**pp<='9')) n = n*10 + *((*pp)++) - '0';
if (n>max_value) {
*pstatus = -4; /* Cardinal outside range */
return 0;
}
return n;
}
/* Bind to a TCP port
** ------------------
**
** On entry,
** tsap is a string explaining where to take data from.
** "" means data is taken from stdin.
** "*:1729" means "listen to anyone on port 1729"
**
** On exit,
** returns Negative value if error.
*/
int do_bind(const char * tsap)
{
FD_ZERO(&open_sockets); /* Clear our record of open sockets */
num_sockets = 0;
/* Deal with PASSIVE socket:
**
** A passive TSAP is one which has been created by the inet daemon.
** It is indicated by a void TSAP name. In this case, the inet
** daemon has started this process and given it, as stdin, the connection
** which it is to use.
*/
if (*tsap == 0) { /* void tsap => passive */
dynamic_allocation = FALSE; /* not dynamically allocated */
role = passive; /* Passive: started by daemon */
#ifdef vms
{ unsigned short channel; /* VMS I/O channel */
struct string_descriptor { /* This is NOT a proper descriptor*/
int size; /* but it will work. */
char *ptr; /* Should be word,byte,byte,long */
} sys_input = {10, "SYS$INPUT:"};
int status; /* Returned status of assign */
extern int sys$assign();
status = sys$assign(&sys_input, &channel, 0, 0);
com_soc = channel; /* The channel is stdin */
CTRACE(tfp, "IP: Opened PASSIVE socket %d\n", channel);
return -BAD(status);
}
#else
com_soc = 0; /* The channel is stdin */
CTRACE(tfp, "IP: PASSIVE socket 0 assumed from inet daemon\n");
return 0; /* Good */
#endif
/* Parse the name (if not PASSIVE)
*/
} else { /* Non-void TSAP */
char *p; /* pointer to string */
char *q;
struct hostent *phost; /* Pointer to host - See netdb.h */
char buffer[256]; /* One we can play with */
register struct sockaddr_in* sin = &soc_address;
strcpy(buffer, tsap);
p = buffer;
/* Set up defaults:
*/
sin->sin_family = AF_INET; /* Family = internet, host order */
sin->sin_port = 0; /* Default: new port, */
dynamic_allocation = TRUE; /* dynamically allocated */
role = passive; /* by default */
/* Check for special characters:
*/
if (*p == WILDCARD) { /* Any node */
role = master;
p++;
}
/* Strip off trailing port number if any:
*/
for(q=p; *q; q++)
if (*q==':') {
int status;
*q++ = 0; /* Terminate node string */
sin->sin_port = htons((unsigned short)parse_cardinal(
&status, &q, (unsigned int)65535));
if (status<0) return status;
if (*q) return -2; /* Junk follows port number */
dynamic_allocation = FALSE;
break; /* Exit for loop before we skip the zero */
} /*if*/
/* Get node name:
*/
if (*p == 0) {
sin->sin_addr.s_addr = INADDR_ANY; /* Default: any address */
} else if (*p>='0' && *p<='9') { /* Numeric node address: */
sin->sin_addr.s_addr = inet_addr(p); /* See arpa/inet.h */
} else { /* Alphanumeric node name: */
phost=gethostbyname(p); /* See netdb.h */
if (!phost) {
CTRACE(tfp, "IP: Can't find internet node name `%s'.\n",p);
return inet_status("gethostbyname"); /* Fail? */
}
memcpy(&sin->sin_addr, phost->h_addr, phost->h_length);
}
CTRACE(tfp,
"TCP: Parsed address as port %4x, inet %d.%d.%d.%d\n",
(unsigned int)ntohs(sin->sin_port),
(int)*((unsigned char *)(&sin->sin_addr)+0),
(int)*((unsigned char *)(&sin->sin_addr)+1),
(int)*((unsigned char *)(&sin->sin_addr)+2),
(int)*((unsigned char *)(&sin->sin_addr)+3));
} /* scope of p */
/* Master socket for server:
*/
if (role == master) {
/* Create internet socket
*/
master_soc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (master_soc<0)
return inet_status("socket");
CTRACE(tfp, "IP: Opened socket number %d\n", master_soc);
/* If the port number was not specified, then we search for a free one.
*/
if (dynamic_allocation) {
unsigned short try;
for (try=FIRST_TCP_PORT; try<=LAST_TCP_PORT; try++) {
soc_address.sin_port = htons(try);
if (bind(master_soc,
(struct sockaddr*)&soc_address, /* Cast to generic sockaddr */
sizeof(soc_address)) == 0)
break;
if (try == LAST_TCP_PORT)
return inet_status("bind");
}
CTRACE(tfp, "IP: Bound to port %u.\n",
ntohs(soc_address.sin_port));
} else { /* Port was specified */
if (bind(master_soc,
(struct sockaddr*)&soc_address, /* Case to generic address */
sizeof(soc_address))<0)
return inet_status("bind");
}
if (listen(master_soc, LISTEN_BACKLOG)<0)
return inet_status("listen");
CTRACE(tfp, "TCP: Master socket(), bind() and listen() all OK\n");
FD_SET(master_soc, &open_sockets);
if ((master_soc+1) > num_sockets) num_sockets=master_soc+1;
return master_soc;
} /* if master */
return -1; /* unimplemented role */
} /* do_bind */
/* Handle incomming messages do_action()
** -------------------------
**
** On entry:
**
** timeout -1 for infinite, 0 for poll, else in units of 10ms
**
** On exit,
** returns The status of the operation, <0 if failed.
**
*/
#ifdef __STDC__
PRIVATE int do_action(void)
#else
PRIVATE int do_action()
#endif
{
int tcp_status; /* <0 if error, in general */
int timeout = -1; /* No timeout required but code exists */
for(;;) {
/* If it's a master socket, then find a slave:
*/
if (role == master) {
#ifdef SELECT
fd_set read_chans;
fd_set write_chans;
fd_set except_chans;
int nfound; /* Number of ready channels */
struct timeval max_wait; /* timeout in form for select() */
FD_ZERO(&write_chans); /* Clear the write mask */
FD_ZERO(&except_chans); /* Clear the exception mask */
/* If timeout is required, the timeout structure is set up. Otherwise
** (timeout<0) a zero is passed instead of a pointer to the struct timeval.
*/
if (timeout>=0) {
max_wait.tv_sec = timeout/100;
max_wait.tv_usec = (timeout%100)*10000;
}
for (com_soc=-1; com_soc<0;) { /* Loop while connections keep coming */
/* The read mask expresses interest in the master channel for incomming
** connections) or any slave channel (for incomming messages).
*/
/* Wait for incoming connection or message
*/
read_chans = open_sockets; /* Read on all active channels */
if (TRACE) printf(
"TcpAccess: Waiting for connection or message. (Mask=%x hex, max=%x hex).\n",
*(int *)(&read_chans), num_sockets);
nfound=select(num_sockets, &read_chans,
&write_chans, &except_chans,
timeout >= 0 ? &max_wait : 0);
if (nfound<0) return inet_status("accept");
if (nfound==0) return 0; /* Timeout */
/* If an incomming connection has arrived, accept the new socket:
*/
if (FD_ISSET(master_soc, &read_chans)) {
CTRACE(tfp, "TCP: New incomming connection:\n");
tcp_status = accept(master_soc,
(struct sockaddr *)&soc_address,
&soc_addrlen);
if (tcp_status<0)
return inet_status("accept");
CTRACE(tfp, "TCP: Accepted new socket %d\n",
tcp_status);
FD_SET(tcp_status, &open_sockets);
if ((tcp_status+1) > num_sockets) num_sockets=tcp_status+1;
nfound--;
} /* end if new connection */
/* If a message has arrived on one of the channels, take that channel:
*/
if(nfound) {
int i;
for(i=0;;i++)
if(FD_ISSET(i, &read_chans)) {
if (TRACE) printf("Got new socket %i\n", i);
com_soc = i; /* Got one! */
break;
}
break; /* Found input socket com_soc */
} /* if message waiting */
} /* loop on event */
#else /* SELECT not supported */
if (com_soc<0) { /* No slaves: must accept */
CTRACE(tfp, "TCP: Waiting for incomming connection...\n");
tcp_status = accept(master_soc,
&rsoc->mdp.soc_tcp.soc_address,
&rsoc->mdp.soc_tcp.soc_addrlen);
if (tcp_status<0)
return inet_status("accept");
com_soc = tcp_status; /* socket number */
CTRACE(tfp, "TCP: Accepted socket %d\n", tcp_status);
} /* end if no slaves */
#endif
} /* end if master */
/* com_soc is now valid for read */
/* Read the message now on whatever channel there is:
*/
CTRACE(tfp,"TCP: Reading socket %d\n", com_soc);
tcp_status=handle(com_soc);
if(tcp_status<=0) { /* EOF or error */
if (tcp_status<0) { /* error */
CTRACE(tfp, "TCP: Error %i handling incomming message (errno=%i).\n",
tcp_status, errno);
/* DONT return inet_status("netread"); error */
} else {
CTRACE(tfp, "TCP: Socket %d disconnected by peer\n",
com_soc);
}
if (role==master) {
NETCLOSE(com_soc);
FD_CLR(com_soc, &open_sockets);
} else
#ifdef VM
return -69;
;
#else
return -ECONNRESET;
#endif
} /* end if handler got EOF or error */
}; /* for loop */
/*NOTREACHED*/
} /* end do_action */
/* Main program
** ------------
*/
int main(int arc, char*argv[])
{
int status;
status = do_bind("*:2784");
if (status<0) {
printf("Bad setup.\n");
exit(status);
}
status = do_action();
if (status<0) {
printf("Error in server loop.\n");
exit(status);
}
exit(0);
return 0; /* For gcc */
}