commit 951a80ef8d5a3cd2e6c15ec9fef959fed73cbe37 Author: I am not me Date: Wed Mar 13 02:06:49 2013 -0400 NCSA HTTPd 0.5 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..c0174f1 --- /dev/null +++ b/INSTALL @@ -0,0 +1,134 @@ +INSTALLATION +------------ + + Unless you're installing this server standalone, you'll need to be able +to edit /etc/services and /etc/inetd.conf. + + + If you're on a Sun Sparc, a DECstation 3000/5000 or DECstation +AXP, an IBM RS/6000, or an SGI, you can download a binary of the +server from ftp.ncsa.uiuc.edu, /Mosaic/ncsa_httpd. The binaries have +been compiled with the following options: + +All error files are in /usr/local/etc/httpd +The config file is /usr/local/etc/httpd/httpd.conf +The default port number is 80. +The maximum number of aliases is 20. +The name of a directory's index file is index.html. +The server does not truncate lines. +Errors are not logged to syslog. +HTTP accesses are logged to /usr/local/etc/httpd/access_log +The annotation server support is turned off. + + The executables named httpd do not have gopher support, while +those named httpd.gopher do have it. + + If you want to compile the source yourself, do that first. The +httpd.h file should have fairly straightforward instructions for what +you need to configure. The Makefile has various options for different +platforms, once again it should be fairly straightforward. + +If you have any problems, or things you would like to see changed so +as to make them more understandable at this stage, please e-mail +httpd@ncsa.uiuc.edu with suggestions. + + +Once you have a binary +...................... + + +1. Decide where you want your server to live. A good place is +/usr/local/etc/httpd, because that's where the error and config files +are expected to be. We recommend placing your server under the name +/usr/local/etc/httpd/httpd, and that way all of your httpd stuff is in +one place. + +1.5. Decide if you want to run the server standalone, or from inetd. If +you are not sure, running standalone is generally faster. If you are choosing +an inetd based server, skip forward to step 2. + +To use the server standalone, simply execute it with the -s and -p options. The +-p followed by a number tells the server what port to listen to. In order to +listen to ports with a number < 1024, you'll need to be root to run the server +initially. + +Example: +httpd -s -p 8081 + +To start a standalone server listening to port 8081. + +Skip to step 4. + +2. Edit /etc/services, and place a line in there which tells inetd +which port you want your http server to listen to. Make sure the +define for ANN_PORT in httpd.h and this line agree. Example: + +http 80/tcp + + +3. Edit /etc/inetd.conf, and place a line in there which tells inetd +to fire off a server each time someone connects to that port. Example: + +http stream tcp nowait nobody /usr/local/etc/httpd/httpd httpd + +assuming you decided to put the server binary in /usr/local/etc/httpd. +It is an excellent idea to run httpd under user ID 'nobody' (as shown +above), as there's not much damage 'nobody' can do. + + +4. If you are running with access logging enabled, make sure +/usr/local/etc/httpd/access_log (or the appropriate path/file) is +writeable by the user ID being used to run httpd (usually, nobody). +If this file is not writeable by that user ID, httpd will not work. + + +5. Place the error message files where the server is expecting them. +If you used a precompiled binary, create the directory +/usr/local/etc/httpd/errors, and copy all of the html files from this +distribution in there. If you didn't use the precompiled one, create +whatever directory you decided to put them under. Be sure they are +readable. You can feel free to edit the messages to specify problem +report addresses or whatnot. If you are compiling the server from the +source, you can put these wherever you like. + + +6. Create a httpd.conf in /usr/local/etc/httpd/httpd.conf for your +server. If you are compiling from the source, the #define CONFIG_FILE +tells where the server will look for this file by default. The server +can also be started with the -f flag and a filename to override this +default. + +The syntax for the httpd.conf file is very simple. Every line which +begins with a # sign is a comment. Besides that, lines are of the +format: + +alias:realdir + +Where alias is the name which should be seen by the clients, and +realdir is the real server directory which should be mapped to that +location. Examples: + +/foo:/bletch + +Makes it so that all client requests beginning with /foo are mapped to +the real directory /bletch. + +/:/port4/web-docs + +This has the effect of mapping all client requests into the directory +/port4/web-docs, which makes for easy centralization. + +Aliases are cumulative; once you map something, all of your subsequent +aliases will have the previous ones expanded in the alias name before +they themselves are interpreted. For example: + +/:/port4/web-docs +/foo:/port4/web-docs/bletch + +This would re-map all access to /port4/web-docs/foo to +/port4/web-docs/bletch. + +To deny access to a directory, map it to something non-existent like /deny. + + +httpd@ncsa.uiuc.edu diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1455cec --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +# Makefile for NCSA's httpd. + +# For normal machines with ANSI compilers +CC= cc +# For Suns or other non-ANSI platforms +#CC= gcc + +# For optimization +#CFLAGS= -O2 +# For debugging information +#CFLAGS= -g +# If your system does not have strdup(), then do this +# CFLAGS = -O2 -DNEED_STRDUP + +# Place here any flags you may need upon linking, such as a flag to +# prevent dynamic linking (if desired) +LFLAGS= + +# Place here any extra libraries you may need to link to. You +# shouldn't have to. +EXTRA_LIBS= + +# You shouldn't have to edit anything else. + +OBJS=http_config.o httpd.o http_request.o util.o http_dir.o http_gopher.o \ + ann_request.o ann_set.o + +.c.o: + $(CC) -c $(CFLAGS) $(DEFINES) $< + +all: httpd + +httpd: $(OBJS) + $(CC) $(LFLAGS) -o httpd $(OBJS) $(EXTRA_LIBS) + +$(OBJS): Makefile httpd.h + +clean: + rm -f httpd $(OBJS) diff --git a/README b/README new file mode 100644 index 0000000..3d1fbe4 --- /dev/null +++ b/README @@ -0,0 +1,111 @@ +NCSA HTTP server +================ + +Beta Release 0.5 + +Final release for Gopher and Group Annotation features. In future releases, + Gopher support will not reappear, and Group Annotation features will reappear + in a more protocol-supported fashion. + +This code is in the public domain. Specifically, we give to the public +domain all rights for future licensing of the source code, all resale +rights, and all publishing rights. + +We ask, but do not require, that the following message be included in +all derived works: + +Portions developed at the National Center for Supercomputing +Applications at the University of Illinois at Urbana-Champaign. + +THE UNIVERSITY OF ILLINOIS GIVES NO WARRANTY, EXPRESSED OR IMPLIED, +FOR THE SOFTWARE AND/OR DOCUMENTATION PROVIDED, INCLUDING, WITHOUT +LIMITATION, WARRANTY OF MERCHANTABILITY AND WARRANTY OF FITNESS FOR A +PARTICULAR PURPOSE. + + +Now that that's out of the way, we can move on to more interesting +things. + +OVERVIEW +-------- + + You've found the Beta release of NCSA's HTTP server. This code is +in testing and development, but is currently fully functional as a +simple HTTP server. + + +NEW IN RELEASE 0.5 +------------------ + + o Slight bug in gopher support fixed + o Standalone support added + +NEW IN RELEASE 0.4 +------------------ + + o Added annotation server support. See README.GROUP-ANNOTATIONS. + o Added access logging capability. + o Added HTTP/1.0 compatibility (3rd+ args of GET command ignored). + o Changed handling of full hostnames. + o Fixed bug with index.html not being recognized unless a trailing + slash was appended to the directory name. + o Fixed bug with extraneous null appended to end of file data + stream. + o Fixed bug handling directory URL's with trailing slashes. + o Made a little more Ultrix-friendly. + +NEW IN RELEASE 0.3 +------------------ + + o Security hole (should be the last) fixed + o Nearly complete drop-in gopher support. See README.GOPHER for details + o Not being able to find the config file does not send back a path + +NEW IN RELEASE 0.2 +------------------ + + o Two security holes fixed + o Double slashes in root index fixed + o Not being able to find the error files no longer hangs the server + +FEATURES +-------- + + o Directory aliasing and denial. The server can be configured to +map different directories to different places in the filesystem, and +easily deny access to a particular directory or file by mapping it to +something which does not exist. The interface is designed to be simple. + + o Automatic directory index files and on-the-fly index generation. +If the server finds that the client is requesting a directory, it +first checks for a file, by default called "index.html" but which can +be compiled to any name, and if it does not find such a file it +generates an HTML index of the directory and sends it back to the +client. + + o Drop-in gopher support. This server is able to automatically +interpret gopher directory structures and construct, on the fly, HTML +indexes of them. Should be faster than gopherd in HTML mode. + + o Group annotation server capabilities. See +README.GROUP-ANNOTATIONS for more details. + + o Access logging. See httpd.h for more details. + + o Designed to be small and low-impact: the entire source is +3,000 lines, with full gopher, annotation server, and logging support. + +FUTURE PLANS +------------ + + o HTTP2 (HTTP/1.0) support. + + o On the fly uncompression. + +That should be all you need. E-mail problems, suggestions for features +or documentation, cash donations, food, etc. to httpd@ncsa.uiuc.edu. +We encourage you to try and break the server, or compromise its +security, and let us know how you did it so we can fix it. + +Rob, Eric, Marc +httpd@ncsa.uiuc.edu diff --git a/README.GOPHER b/README.GOPHER new file mode 100644 index 0000000..e3bf2b8 --- /dev/null +++ b/README.GOPHER @@ -0,0 +1,66 @@ +This code is in the public domain. Specifically, we give to the public +domain all rights for future licensing of the source code, all resale +rights, and all publishing rights. + +We ask, but do not require, that the following message be included in +all derived works: + +Portions developed at the National Center for Supercomputing +Applications at the University of Illinois at Urbana-Champaign. + +THE UNIVERSITY OF ILLINOIS GIVES NO WARRANTY, EXPRESSED OR IMPLIED, +FOR THE SOFTWARE AND/OR DOCUMENTATION PROVIDED, INCLUDING, WITHOUT +LIMITATION, WARRANTY OF MERCHANTABILITY AND WARRANTY OF FITNESS FOR A +PARTICULAR PURPOSE. + + +OVERVIEW OF GOPHER SUPPORT +-------------------------- + + As of beta release 0.3, this server will automatically generate +HTML listings of a gopher directory without going through gopherd. +This means that . files which are links are processed, and .cap files +are processed. Any request which begins with /gopher-data will be +considered a gopher request if gopher is turned on. Therefore, mapping +/gopher-data to the location of your server's data will allow you to +access /gopher-data and get a gopher listing in return. + + These features are new to 0.3, and I would appreciate any and all +feedback on its usefulness, features and/or lack thereof, problems, +and suggestions. + + NOTE THAT THE GOPHER PROTOCOL IS NOT SUPPORTED in this server; +only httpd is supported; thus, Gopher clients will not be able to talk +to this server. The point of this feature is to allow existing Gopher +data hierarchies to be painlessly transferred into a WWW environment. + +KNOWN PROBLEMS +-------------- + + o Shell scripts are not executed; i.e. a file of type 0 with #! at +the beginning is returned plaintext instead of being executed. + + o There is a slight ordering inconsistency between this server and +the real gopherd, i.e. all numbered are placed first, in order, THEN +the unnumbered files are placed in alphabetical order. The gopher +server fills in the holes with alphabetized entries. + + o Not all gopher features are currently supported, namely the ph +interface. + + o Files of type 's', 'G', 'I', and any files which are special and +need to be treated in a special way are sent back to the client. +Therefore, if the file does not have the proper extension the client +may not recognize it. i.e. if a GIF does not have the .gif extension +NCSA Mosaic probably won't recognize it. + +INSTALLATION +------------ + + See the file INSTALL for normal and gopher installations. Be sure +you define GROK_GOPHER in httpd.h if you are compiling the server, or +that you use the binary with gopher support if you are using a +precompiled one. + + +httpd@ncsa.uiuc.edu diff --git a/README.GROUP-ANNOTATIONS b/README.GROUP-ANNOTATIONS new file mode 100644 index 0000000..f1d105e --- /dev/null +++ b/README.GROUP-ANNOTATIONS @@ -0,0 +1,49 @@ +This code is in the public domain. Specifically, we give to the public +domain all rights for future licensing of the source code, all resale +rights, and all publishing rights. + +We ask, but do not require, that the following message be included in +all derived works: + +Portions developed at the National Center for Supercomputing +Applications at the University of Illinois at Urbana-Champaign. + +THE UNIVERSITY OF ILLINOIS GIVES NO WARRANTY, EXPRESSED OR IMPLIED, +FOR THE SOFTWARE AND/OR DOCUMENTATION PROVIDED, INCLUDING, WITHOUT +LIMITATION, WARRANTY OF MERCHANTABILITY AND WARRANTY OF FITNESS FOR A +PARTICULAR PURPOSE. + + +OVERVIEW OF GROUP ANNOTATION SERVER SUPPORT +------------------------------------------- + +As of beta release 0.4, this server can optionally be compiled to +serve as a group annotation server as well as a normal HTTP server. + +Users of NCSA Mosaic for X version 1.1 and later can set their +annotationServer resource to the string "machine.name:port" (e.g., +"www.foo.com:80") to take advantage of a given group annotation +server. Other WWW browsers may support use of group annotation +servers in the future. + +If you wish to set up httpd to act as a group annotation server, +#define ANNOTATIONS in httpd.h and set ANN_DIR, ANN_PORT, and +ANN_VIRTUAL_DIR to appropriate values. Make sure that the directory +named by ANN_DIR (and virtually named by ANN_VIRTUAL_DIR) is writeable +by the user ID you're running httpd under (normally 'nobody'), and +make sure enough disk space is free in the partition holding that +directory to handle anticipated group annotation activity. + +THE GROUP ANNOTATION SUPPORT OR PROTOCOL MAY CHANGE AT ANY TIME. IT +IS NOT AN OFFICIAL OR SUPPORTED PROTOCOL. Among other things, we have +yet to address matters of scale, server-based logging, tracking, and +notification activities, garbage collection/expiration, and +authentication. The current server is merely an experimental proof of +concept, and it works fine given its design restraints. + +We would greatly appreciate being notified of any serious use of the +group annotation capability in research, educational, academic, or +commercial environments, as well as general comments and suggestions. + + +httpd@ncsa.uiuc.edu diff --git a/ann_request.c b/ann_request.c new file mode 100644 index 0000000..5429482 --- /dev/null +++ b/ann_request.c @@ -0,0 +1,729 @@ +/* + * ann_request.c: functions to get and process annotation requests + * + * Eric Bina + * + */ + + +#include "httpd.h" +#include + + +#ifdef ANNOTATIONS + +/* + * All the recognized arguments that you can pass in an annotation + * request. + */ +static char *ArgUrl, *ArgGroup, *ArgUser, *ArgTitle, *ArgDate, *ArgAudio; +static int ArgLength; + + + +#define HASH_TABLE_SIZE 1037 + + +/* + * Hash a url to a number used to name a log file. + */ +static int HashUrl (char *url) +{ + int len, i, val; + + if (!url) + return 0; + len = strlen (url); + if (!len) + return 0; + val = 0; + for (i = 0; i < 10; i++) + val += url[(i * val + 7) % len]; + + return val % HASH_TABLE_SIZE; +} + + +#define BUFF_SIZE 8192 + +/* + * Function to read from the passed FILE * until the characer + * cstop is reached, EOF, or LINEFEED. Return the length of + * the value read (not counting the trailing '\0') in sz. + * If escapes is 1, backslash escapes the character immediately after it. + */ +char *readto(FILE *in, char cstop, int *sz, int escapes) +{ + char *buf; + int i, size; + + *sz = 0; + buf = (char *)malloc(BUFF_SIZE * sizeof(char)); + if (buf == NULL) + { + return(NULL); + } + for (i=0; i\n"); +fprintf(out, "Got a Set-Url-Annotations Request\n"); +fprintf(out, "URL = %s\nGroup = %s\nUser = %s\nDate = %s\n", +ArgUrl, ArgGroup, ArgUser, ArgDate); +fprintf(out, "Length = %d\n", ArgLength); +*/ + data = NULL; + /* + * Read the data for this new annotation. + */ + if (ArgLength > 0) + { + data = (char *)malloc(ArgLength * sizeof(char)); + if (data == NULL) + { + client_error(out, BAD_ANN_REQUEST); + } + ret = fread(data, sizeof(char), ArgLength, in); + if (ret != ArgLength) + { + client_error(out, BAD_ANN_REQUEST); + } + } + + /* + * Lock the logfile prepratory to setting the new annotation. + */ + hash = HashUrl(ArgUrl); + lptr = GetLogData(hash); + if (lptr == NULL) + { + client_error(out, ANN_SERVER_ERROR); + } + indx = MaxIndex(lptr); + + /* + * If this is an audio annotation, make the real sound file it + * points to, and then put in a standard text annotation. + */ + if (ArgAudio != NULL) + { + char *url; + + /* Looks like ArgAudio is a string corresponding to + the type of audio -- we should use this. @@@ */ + url = WriteAudioData(hash, (indx + 1), + data, ArgLength, ArgAudio, 0); + if (url == NULL) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + if (data) + { + free((char *)data); + } + data = (char *)malloc(strlen("This is an audio annotation.

\n\nTo hear the annotation, go here.

\n") + + strlen(url) + 1); + if (data == NULL) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + sprintf(data, "This is an audio annotation.

\n\nTo hear the annotation, go here.

\n", url); + ArgLength = strlen(data) + 1; + free(url); + } + + /* + * Write the annotation, modify and write the log. + * Unlock the lock, and free memory. + */ + ret = WriteAnnData(ArgUrl, hash, (indx + 1), + ArgTitle, ArgUser, ArgDate, data, ArgLength, 0); + if (ret < 0) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + indx = ret; + ret = AddLogData(lptr, indx, ArgUrl); + if (ret != 1) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + ret = WriteLogData(lptr); + if (ret < 0) + { + client_error(out, ANN_SERVER_ERROR); + } + if ((ArgLength > 0)&&(data != NULL)) + { + free(data); + } + FreeLogData(lptr); +} + + +/* + * We know we have received a GET_ANN request. Process it. + */ +void Get_request(FILE *in, FILE *out) +{ + int i; + int ret, hash; + LogData *lptr; + int *alist, acnt; + + /* + * Read the args, and make sure we have a URL to + * get the annotations on. + */ + ReadArgs(in, out); + if (ArgUrl == NULL) + { + client_error(out, BAD_ANN_REQUEST); + } +/* +fprintf(out, "

\n"); +fprintf(out, "Got a Get-Url-Annotations Request\n"); +fprintf(out, "URL = %s\nGroup = %s\nUser = %s\nDate = %s\n", +ArgUrl, ArgGroup, ArgUser, ArgDate); +fprintf(out, "Length = %d\n", ArgLength); +*/ + + /* + * Read and lock the log file. + */ + hash = HashUrl(ArgUrl); + lptr = GetLogData(hash); + if (lptr == NULL) + { + client_error(out, ANN_SERVER_ERROR); + } + + ret = FindLogData(lptr, ArgUrl, &alist, &acnt); + /* + * If there are annotation on this URL, we need to read each one, + * and extract the title, user, and date to make the list + * of group annotation to be placed at the end of the + * document. + */ + if ((ret > 0)&&(acnt > 0)) + { + char *host; + + host = full_hostname(); + fprintf(out, "<H2>Group Annotations</H2>\n"); + fprintf(out, "<UL>\n"); + for (i=0; i<acnt; i++) + { + ret = ReadAnnData(hash, alist[i], + &ArgTitle, &ArgUser, &ArgDate); + if (ret != 1) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + fprintf(out, "<LI> <A HREF=\"http://%s:%d%s/%d-%d.html\">%s</A> (%s)\n", + host, ANN_PORT, ANN_VIRTUAL_DIR, hash, + alist[i], + ArgTitle, ArgDate); + } + fprintf(out, "</UL>\n"); + } + else + { + } + + UnlockIt(); +} + + +/* + * We know we have received a CHANGE_ANN request. Process it. + */ +void Change_request(FILE *in, FILE *out) +{ + int i, ret, hash; + LogData *lptr; + char *file, *data, *ptr; + char *url; + + /* + * Read the args, and make sure we have a URL to + * change the annotation on. + */ + ReadArgs(in, out); + if (ArgUrl == NULL) + { + client_error(out, BAD_ANN_REQUEST); + } +/* +fprintf(out, "<PRE>\n"); +fprintf(out, "Got a Delete-Url-Annotations Request\n"); +fprintf(out, "URL = %s\nGroup = %s\nUser = %s\nDate = %s\n", +ArgUrl, ArgGroup, ArgUser, ArgDate); +fprintf(out, "Length = %d\n", ArgLength); +*/ + data = NULL; + /* + * Get the data for the new annotation. + */ + if (ArgLength > 0) + { + data = (char *)malloc(ArgLength * sizeof(char)); + if (data == NULL) + { + client_error(out, BAD_ANN_REQUEST); + } + ret = fread(data, sizeof(char), ArgLength, in); + if (ret != ArgLength) + { + client_error(out, BAD_ANN_REQUEST); + } + } + + + /* + * Construct the full path to the annotation to + * be changed. + */ + file = (char *)malloc(MAX_STRING_LEN * sizeof(char)); + if (file == NULL) + { + client_error(out, BAD_ANN_REQUEST); + } + ptr = strrchr(ArgUrl, '/'); + if (ptr == NULL) + { + sprintf(file, "%s/%s", ANN_DIR, ArgUrl); + if (sscanf(ArgUrl, "%d-%d.html", &hash, &i) != + 2) + { + free(file); + client_error(out, BAD_ANN_REQUEST); + } + } + else + { + sprintf(file, "%s%s", ANN_DIR, ptr); + ptr++; + if (sscanf(ptr, "%d-%d.html", &hash, &i) != + 2) + { + free(file); + client_error(out, BAD_ANN_REQUEST); + } + } + + /* + * Read and lock the logfile. + */ + lptr = GetLogData(hash); + if (lptr == NULL) + { + client_error(out, ANN_SERVER_ERROR); + } + + url = HashtoUrl(lptr, i); + if (url == NULL) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + + /* + * Remove the old annotation. + */ + ret = unlink(file); + if (ret != 0) + { + fprintf(out, "Deletion of %s failed.\n", file); + } + else + { + fprintf(out, "Deletion of %s success!\n", file); + } + free(file); + + /* + * Write the new annotation. + */ + ret = WriteAnnData(url, hash, i, + ArgTitle, ArgUser, ArgDate, data, ArgLength, 1); + free(url); + if (ret < 0) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + + UnlockIt(); + + free(data); +} + + +/* + * We know we have received a DELETE_ANN request. Process it. + */ +void Delete_request(FILE *in, FILE *out) +{ + int i, ret, hash; + char *data, *ptr; + LogData *lptr; + + /* + * Read the args, and make sure we have a URL to + * delete + */ + ReadArgs(in, out); + if (ArgUrl == NULL) + { + client_error(out, BAD_ANN_REQUEST); + } +/* +fprintf(out, "<PRE>\n"); +fprintf(out, "Got a Delete-Url-Annotations Request\n"); +fprintf(out, "URL = %s\nGroup = %s\nUser = %s\nDate = %s\n", +ArgUrl, ArgGroup, ArgUser, ArgDate); +fprintf(out, "Length = %d\n", ArgLength); +*/ + + /* + * Construct the full path to the annotation to be deleted. + */ + data = (char *)malloc(MAX_STRING_LEN * sizeof(char)); + if (data == NULL) + { + client_error(out, BAD_ANN_REQUEST); + } + ptr = strrchr(ArgUrl, '/'); + if (ptr == NULL) + { + sprintf(data, "%s/%s", ANN_DIR, ArgUrl); + if (sscanf(ArgUrl, "%d-%d.html", &hash, &i) != + 2) + { + free(data); + client_error(out, BAD_ANN_REQUEST); + } + } + else + { + sprintf(data, "%s%s", ANN_DIR, ptr); + ptr++; + if (sscanf(ptr, "%d-%d.html", &hash, &i) != + 2) + { + free(data); + client_error(out, BAD_ANN_REQUEST); + } + } + + /* + * Read an lock the log file + */ + lptr = GetLogData(hash); + if (lptr == NULL) + { + client_error(out, ANN_SERVER_ERROR); + } + + /* + * Delete this annotation from the logfile. + */ + ret = DeleteLogData(lptr, i); + if (ret != 1) + { + UnlockIt(); + client_error(out, ANN_SERVER_ERROR); + } + + /* + * Delete the actual annotation, and any associated + * audio annotations. + */ + ret = unlink(data); + if (ret != 0) + { + fprintf(out, "Deletion of %s failed.\n", data); + } + else + { + char afile[MAX_STRING_LEN]; + + fprintf(out, "Deletion of %s success!\n", data); + /* + * Remove any associated audio files + */ + sprintf(afile, "%s.au", data); + unlink(afile); + sprintf(afile, "%s.aiff", data); + unlink(afile); + } + free(data); + + /* + * Write the new log, unlock it, and free the data. + */ + ret = WriteLogData(lptr); + if (ret < 0) + { + client_error(out, ANN_SERVER_ERROR); + } + FreeLogData(lptr); +} + +#endif /* ANNOTATIONS */ diff --git a/ann_set.c b/ann_set.c new file mode 100644 index 0000000..ab833cc --- /dev/null +++ b/ann_set.c @@ -0,0 +1,1052 @@ +#include "httpd.h" + +#ifdef ANNOTATIONS + +#define NCSA_GROUP_ANNOTATION_FORMAT_ONE "<ncsa-group-annotation-format-1>" + +#define BUFF_SIZE 8192 + + +/* + * This modulke does all the file locking using flock(). + * Lock is the file descriptor of the lock, when a lock is held, and + * is -1 when no lock is held. + */ +static int Lock = -1; + + + +/* + * Attempt to acquire a lock of the passed file descriptor. + * flock() should block until the lock can be gotten. + */ +static int LockIt(int fd) +{ + int ret; + + ret = flock(fd, LOCK_EX); + if (ret == 0) + { + Lock = fd; + } + else + { + Lock = -1; + } + return(ret); +} + + +/* + * If a lock is held, release it. + */ +void UnlockIt(void) +{ + if (Lock != -1) + { + flock(Lock, LOCK_UN); + close(Lock); + Lock = -1; + } +} + + +/* + * flock() on some systems requires us to not use the buffered I/O. + * So this is a non-buffered I/O function to read from the file descritor + * until the character cstop, a newline, or the EOF is encountered. + */ +char *GetUpTo(int fd, char cstop) +{ + char *buf; + int i, size; + + buf = (char *)malloc(BUFF_SIZE * sizeof(char)); + if (buf == NULL) + { + return(NULL); + } + + for (i=0; i<BUFF_SIZE; i++) + { + int ret; + char val; + + ret = read(fd, &val, 1); + if (ret != 1) + { + return(NULL); + } + if (val == cstop) + { + buf[i] = '\0'; + break; + } + else if (val == '\n') + { + buf[i] = '\0'; + break; + } + buf[i] = val; + } + size = i; + while (i == BUFF_SIZE) + { + char *tptr; + + tptr = buf; + buf = (char *)malloc((size + BUFF_SIZE) * sizeof(char)); + if (buf == NULL) + { + return(NULL); + } + bcopy(tptr, buf, size); + free(tptr); + tptr = (char *)(buf + size); + for (i=0; i<BUFF_SIZE; i++) + { + int ret; + char val; + + ret = read(fd, &val, 1); + if (ret != 1) + { + return(NULL); + } + + if (val == cstop) + { + tptr[i] = '\0'; + break; + } + else if (val == '\n') + { + tptr[i] = '\0'; + break; + } + tptr[i] = val; + } + size += i; + } + return(buf); +} + + + + + + +/* + * This is the only place locks are acquired. It is called as part of + * any annotation action (Set, Delete, Change, Request). I originally did + * not have requests for all annotations on a url locking the log file since + * it was a read only operation, but I didn't want the file changing + * halfway through a read, so now it locks. + */ +LogData *GetLogData(int hash) +{ + char filename[256]; + char *buf; + int fd; + LogData *lptr; + int i, cnt, acnt, indx, error; + + lptr = (LogData *)malloc(sizeof(LogData)); + if (lptr == NULL) + { + return(NULL); + } + + lptr->hash = hash; + lptr->cnt = 0; + lptr->logs = NULL; + + sprintf(filename, "%s/%d.log", ANN_DIR, hash); + fd = open(filename, O_RDWR); + + /* + * No log file. This is the first annotation on this url. + */ + if (fd < 0) + { + return(lptr); + } + + /* + * Lock the log file. + */ + i = LockIt(fd); + if (i != 0) + { + close(fd); + return(NULL); + } + + /* + * Read how many urls have hashed to this log file. + */ + buf = GetUpTo(fd, '\n'); + if (buf == NULL) + { + return(lptr); + } + cnt = atoi(buf); + free(buf); + + if ((cnt < 1)||((lptr->logs = + (LogRec *)malloc(sizeof(LogRec) * cnt)) == NULL)) + { + return(lptr); + } + + error = 0; + for (i=0; i<cnt; i++) + { + int l; + char *url; + + /* + * read the url + */ + url = GetUpTo(fd, ' '); + + /* + * read the number of annotations on this url + */ + buf = GetUpTo(fd, ' '); + if (buf == NULL) + { + error = 1; + break; + } + acnt = atoi(buf); + free(buf); + + /* + * Read the annotation numbers, and fill in the log record. + */ + if ((url == NULL)|| + (*url == '\0')|| + (acnt <= 0)) + { + error = 1; + break; + } + lptr->logs[i].url = strdup(url); + free(url); + if (lptr->logs[i].url == NULL) + { + error = 1; + break; + } + lptr->logs[i].ann_array = (int *)malloc(acnt * sizeof(int)); + if (lptr->logs[i].ann_array == NULL) + { + error = 1; + break; + } + lptr->logs[i].acnt = acnt; + for (l=0; l<acnt; l++) + { + buf = GetUpTo(fd, ' '); + if (buf == NULL) + { + error = 1; + break; + } + indx = atoi(buf); + free(buf); + lptr->logs[i].ann_array[l] = indx; + } + buf = GetUpTo(fd, '\n'); + if ((error)||(buf == NULL)) + { + break; + } + } + + /* + * Something failed, free everything up, and return + * an empty log. + */ + if (error) + { + int l; + + for (l=0; l<cnt; l++) + { + if (lptr->logs[l].url != NULL) + { + free(lptr->logs[l].url); + } + if (lptr->logs[l].ann_array != NULL) + { + free(lptr->logs[l].ann_array); + } + } + free(lptr->logs); + lptr->logs = NULL; + return(lptr); + } + + lptr->cnt = cnt; + return(lptr); +} + + +/* + * Look through the logdata, and find all the annotations on + * a given url. + */ +int FindLogData(LogData *lptr, char *url, int **alist, int *acnt) +{ + int i, indx, cnt; + int *aptr; + + *acnt = 0; + *alist = NULL; + if ((lptr == NULL)||(lptr->cnt == 0)) + { + return(-1); + } + + /* + * Find the url in the log data. + */ + cnt = 0; + for (i=0; i < lptr->cnt; i++) + { + if ((lptr->logs[i].url != NULL)&& + (strcmp(lptr->logs[i].url, url) == 0)) + { + break; + } + } + + /* + * The url is not in this log + */ + if (i == lptr->cnt) + { + return(0); + } + + indx = i; + + /* + * Fill in the array of annotations, and return. + */ + cnt = lptr->logs[indx].acnt; + if (cnt == 0) + { + return(0); + } + aptr = (int *)malloc(cnt * sizeof(int)); + if (aptr == NULL) + { + return(-1); + } + for (i=0; i < cnt; i++) + { + aptr[i] = lptr->logs[indx].ann_array[i]; + } + *alist = aptr; + *acnt = cnt; + return(cnt); +} + + +/* + * Add a new annotation to a url in a given logdata + */ +int AddLogData(LogData *lptr, int indx, char *url) +{ + char **tarray; + int i, cnt; + LogRec *lrec; + + if (lptr == NULL) + { + return(0); + } + + /* + * Find the url in the logdata + */ + lrec = NULL; + for (i=0; i < lptr->cnt; i++) + { + if ((lptr->logs[i].url != NULL)&& + (strcmp(lptr->logs[i].url, url) == 0)) + { + lrec = &(lptr->logs[i]); + break; + } + } + + /* + * This url does not currently have an entry in this logdata. + * Make a new entry for it. Allocate space for all the current + * urls, plus one. Copy all the old data forward, fill in the + * new data, then free up the old space. + */ + if (lrec == NULL) + { + if ((lrec = (LogRec *)malloc((lptr->cnt + 1) * sizeof(LogRec))) + == NULL) + { + return(0); + } + lrec[lptr->cnt].url = strdup(url); + if (lrec[lptr->cnt].url == NULL) + { + return(0); + } + if ((lrec[lptr->cnt].ann_array = (int *)malloc(sizeof(int))) + == NULL) + { + return(0); + } + bcopy(lptr->logs, lrec, (lptr->cnt * sizeof(LogRec))); + free((char *)lptr->logs); + lptr->logs = lrec; + lrec = &(lptr->logs[lptr->cnt]); + lptr->cnt++; + lrec->acnt = 1; + lrec->ann_array[0] = indx; + } + /* + * Add this annotation the the ones already on this url. + */ + else + { + int *iptr; + + /* + * If this annotation is already recorded on this url, do + * nothing and return. + */ + for (i=0; i < lrec->acnt; i++) + { + if (lrec->ann_array[i] == indx) + { + return(0); + } + } + + /* + * Make room for the new annotation. Copy the old + * annotations forward, and fill in the new one. + * free the old space. + */ + iptr = (int *)malloc((lrec->acnt + 1) * sizeof(int)); + if (iptr == NULL) + { + return(0); + } + bcopy(lrec->ann_array, iptr, (lrec->acnt * sizeof(int))); + free((char *)lrec->ann_array); + lrec->ann_array = iptr; + lrec->ann_array[lrec->acnt] = indx; + lrec->acnt++; + } + + return(1); +} + + +/* + * Give a log data structure and the index, find the url + * of the document this annotation resides on. + */ +char *HashtoUrl(LogData *lptr, int indx) +{ + char *url; + int i, j; + + if (lptr == NULL) + { + return(0); + } + + for (i=0; i < lptr->cnt; i++) + { + for (j=0; j < lptr->logs[i].acnt; j++) + { + if (lptr->logs[i].ann_array[j] == indx) + { + url = strdup(lptr->logs[i].url); + return(url); + } + } + } + return(NULL); +} + + +/* + * Write out the log data to a logfile. + */ +int WriteLogData(LogData *lptr) +{ + char filename[256]; + char buf[BUFF_SIZE]; + int fd; + int i, j; + + if (lptr == NULL) + { + return(-1); + } + + sprintf(filename, "%s/%d.log", ANN_DIR, lptr->hash); + + /* + * If there are no more annotations tolog here, remove the + * file, and unlock it (if locked). + */ + if (lptr->cnt == 0) + { + unlink(filename); + UnlockIt(); + return(0); + } + + /* + * Move to the beginning of a locked log file, or create a + * new one if this is the first annotation. + */ + if (Lock != -1) + { + fd = Lock; + lseek(fd, 0, 0); + } + else + { + fd = open(filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + } + if (fd < 0) + { + return(-1); + } + + /* + * Write out the log file. We can't use buffered I/O here + * because the file is locked with flock(). + */ + sprintf(buf, "%d\n", lptr->cnt); + write(fd, buf, strlen(buf)); + for (i=0; i < lptr->cnt; i++) + { + sprintf(buf, "%s %d ", lptr->logs[i].url, lptr->logs[i].acnt); + write(fd, buf, strlen(buf)); + for (j=0; j < lptr->logs[i].acnt; j++) + { + sprintf(buf, "%d ", lptr->logs[i].ann_array[j]); + write(fd, buf, strlen(buf)); + } + sprintf(buf, "\n"); + write(fd, buf, strlen(buf)); + } + + /* + * Truncate the file in case is has become shorter, then unlock + * and close it. + */ + ftruncate(fd, tell(fd)); + UnlockIt(); + return(1); +} + + +/* + * Free up all the memory allocated to store a read in logfile. +*/ +void FreeLogData(LogData *lptr) +{ + int l; + + for (l=0; l < lptr->cnt; l++) + { + if (lptr->logs[l].url != NULL) + { + free(lptr->logs[l].url); + } + if (lptr->logs[l].ann_array != NULL) + { + free((char *)(lptr->logs[l].ann_array)); + } + } + free((char *)(lptr->logs)); + free((char *)lptr); +} + + +/* + * Write a new audio annotation with the passed index. If no_change is + * not set, we are allowed to change the index to find a filename that + * isn't already in use. + * On success return the url of the new annotation, on failure + * return NULL. + */ +char *WriteAudioData(int hash, int indx, char *data, int len, char *type, int no_change) +{ + char filename[256]; + char *url; + FILE *fp; + int orig_i; + char *host; + + host = full_hostname (); + + /* + * Find the next unused filename in a sequence + * starting with indx. + */ + orig_i = indx; + sprintf(filename, "%s/%d-%d.html", ANN_DIR, hash, indx); + fp = fopen(filename, "r"); + while (fp != NULL) + { + fclose(fp); + indx++; + sprintf(filename, "%s/%d-%d.html", ANN_DIR, hash, indx); + fp = fopen(filename, "r"); + } + + if ((orig_i != indx)&&(no_change)) + { + return(NULL); + } + + /* + * Write the sound data. Currently either .au or .aiff + */ + sprintf(filename, "%s/%d-%d.html.%s", ANN_DIR, hash, indx, type); + fp = fopen(filename, "w"); + if (fp == NULL) + { + return(NULL); + } + fwrite(data, sizeof(char), len, fp); + fclose(fp); + + /* + * Construct and return the url for this annotation. + */ + url = (char *)malloc(strlen(filename) + 512); + if (url == NULL) + { + return(NULL); + } + sprintf(url, "http://%s:%d%s/%d-%d.html.%s", + host, ANN_PORT, ANN_VIRTUAL_DIR, hash, indx, type); + return(url); +} + + +/* + * Write a new annotation with the passed index. If no_change is + * not set, we are allowed to change the index to find a filename that + * isn't already in use. + * On success return the index of the annotation actually written, on + * failure return -1. + */ +int WriteAnnData(char *url, int hash, int indx, char *title, char *user, char *date, char *data, int len, int no_change) +{ + char filename[256]; + FILE *fp; + int orig_i; + + /* + * Find the next unused filename in a sequence + * starting with indx. + */ + orig_i = indx; + sprintf(filename, "%s/%d-%d.html", ANN_DIR, hash, indx); + fp = fopen(filename, "r"); + while (fp != NULL) + { + fclose(fp); + indx++; + sprintf(filename, "%s/%d-%d.html", ANN_DIR, hash, indx); + fp = fopen(filename, "r"); + } + + if ((orig_i != indx)&&(no_change)) + { + return(-1); + } + + /* + * Write the annotation. + */ + fp = fopen(filename, "w"); + if (fp == NULL) + { + return(-1); + } + fprintf(fp, "%s\n", NCSA_GROUP_ANNOTATION_FORMAT_ONE); + fprintf(fp, "<title>%s</title>\n", title); + fprintf(fp, "<h1>%s</h1>\n", title); + fprintf(fp, "<address>%s</address>\n", user); + fprintf(fp, "<address>%s</address>\n", date); + fprintf(fp, "<address>(<a HREF=\"%s\">Back</a> to annotated document)</address>", url); + fprintf(fp, "______________________________________\n"); + fprintf(fp, "<pre>\n"); + fwrite(data, sizeof(char), len, fp); + fclose(fp); + return(indx); +} + + +/* + * read the requested annotation, extracting title, user, and date. + */ +int ReadAnnData(int hash, int indx, char **title, char **user, char **date) +{ + char filename[256]; + char *buf; + FILE *fp; + int val; + int size, cnt; + + sprintf(filename, "%s/%d-%d.html", ANN_DIR, hash, indx); + fp = fopen(filename, "r"); + if (fp == NULL) + { + return(0); + } + + /* + * Skip the first line, containing the magic cookie for mosaic + */ + val = fgetc(fp); + while (val != '\n') + { + if (val == EOF) + { + return(0); + } + val = fgetc(fp); + } + + /* + * Skip this line, it is the <TITLE> mark. + */ + val = fgetc(fp); + while (val != '\n') + { + if (val == EOF) + { + return(0); + } + val = fgetc(fp); + } + + /* + * Skip past the <h1> + */ + val = fgetc(fp); + while (val != '>') + { + if (val == EOF) + { + return(0); + } + val = fgetc(fp); + } + + /* + * Read the title + */ + buf = (char *)malloc(MAX_STRING_LEN * sizeof(char)); + if (buf == NULL) + { + return(0); + } + size = MAX_STRING_LEN; + cnt = 0; + val = fgetc(fp); + while (val != '<') + { + if (val == EOF) + { + return(0); + } + buf[cnt] = val; + val = fgetc(fp); + cnt++; + if (cnt == size) + { + char *tptr; + + tptr = (char *)malloc((size + MAX_STRING_LEN) * + sizeof(char)); + if (tptr == NULL) + { + return(0); + } + bcopy(buf, tptr, size); + free(buf); + buf = tptr; + size += MAX_STRING_LEN; + } + } + buf[cnt] = 0; + *title = strdup(buf); + if (*title == NULL) + { + return(0); + } + + /* + * Skip the junk at the end of the title line + */ + val = fgetc(fp); + while (val != '\n') + { + if (val == EOF) + { + return(0); + } + val = fgetc(fp); + } + + /* + * Skip the <address> + */ + val = fgetc(fp); + while (val != '>') + { + if (val == EOF) + { + return(0); + } + val = fgetc(fp); + } + + /* + * Read the user info + */ + buf = (char *)malloc(MAX_STRING_LEN * sizeof(char)); + if (buf == NULL) + { + return(0); + } + size = MAX_STRING_LEN; + cnt = 0; + val = fgetc(fp); + while (val != '<') + { + if (val == EOF) + { + return(0); + } + buf[cnt] = val; + val = fgetc(fp); + cnt++; + if (cnt == size) + { + char *tptr; + + tptr = (char *)malloc((size + MAX_STRING_LEN) * + sizeof(char)); + if (tptr == NULL) + { + return(0); + } + bcopy(buf, tptr, size); + free(buf); + buf = tptr; + size += MAX_STRING_LEN; + } + } + buf[cnt] = 0; + *user = strdup(buf); + if (*user == NULL) + { + return(0); + } + + /* + * skip the junk at the end of the user line. + */ + val = fgetc(fp); + while (val != '\n') + { + if (val == EOF) + { + return(0); + } + val = fgetc(fp); + } + + /* + * skip the <address> + */ + val = fgetc(fp); + while (val != '>') + { + if (val == EOF) + { + return(0); + } + val = fgetc(fp); + } + + /* + * read the date + */ + cnt = 0; + val = fgetc(fp); + while (val != '<') + { + if (val == EOF) + { + return(0); + } + buf[cnt] = val; + val = fgetc(fp); + cnt++; + if (cnt == size) + { + char *tptr; + + tptr = (char *)malloc((size + MAX_STRING_LEN) * + sizeof(char)); + if (tptr == NULL) + { + return(0); + } + bcopy(buf, tptr, size); + free(buf); + buf = tptr; + size += MAX_STRING_LEN; + } + } + buf[cnt] = 0; + *date = strdup(buf); + if (*date == NULL) + { + return(0); + } + + fclose(fp); + return(1); +} + +/* + * find the largest annotation index for this log file. + */ +int MaxIndex(LogData *lptr) +{ + int i, j, indx; + + indx = 0; + + if ((lptr == NULL)||(lptr->cnt == 0)) + { + return(indx); + } + + for (i=0; i < lptr->cnt; i++) + { + for (j=0; j < lptr->logs[i].acnt; j++) + { + if (lptr->logs[i].ann_array[j] > indx) + { + indx = lptr->logs[i].ann_array[j]; + } + } + } + + /* + * WARNING, ARBITRARY LIMIT! 2^14 is the max index + */ + if (indx > (0x01 << 14)) + { + indx = 0; + } + return(indx); +} + +/* + * Delete the passed index from the log data. + */ +int DeleteLogData(LogData *lptr, int indx) +{ + int i, j, found; + + if ((lptr == NULL)||(lptr->cnt == 0)) + { + return(0); + } + + found = 0; + for (i=0; i < lptr->cnt; i++) + { + for (j=0; j < lptr->logs[i].acnt; j++) + { + if (lptr->logs[i].ann_array[j] == indx) + { + found = 1; + break; + } + } + if (found) + { + break; + } + } + if (!found) + { + return(0); + } + + if (lptr->logs[i].acnt > 1) + { + lptr->logs[i].ann_array[j] = + lptr->logs[i].ann_array[lptr->logs[i].acnt - 1]; + lptr->logs[i].acnt--; + return(1); + } + else + { + if (lptr->logs[i].ann_array != NULL) + { + free((char *)lptr->logs[i].ann_array); + } + if (lptr->logs[i].url != NULL) + { + free((char *)lptr->logs[i].url); + } + + if (lptr->cnt > 1) + { + if (i == (lptr->cnt - 1)) + { + lptr->cnt--; + return(1); + } + else + { + bcopy((char *)&(lptr->logs[lptr->cnt - 1]), + (char *)&(lptr->logs[i]), + sizeof(LogRec)); + lptr->cnt--; + return(1); + } + } + else + { + free((char *)lptr->logs); + lptr->logs = NULL; + lptr->cnt = 0; + return(1); + } + } +} + + +#endif /* ANNOTATIONS */ diff --git a/contrib/gopher_fixes b/contrib/gopher_fixes new file mode 100644 index 0000000..5c64d4c --- /dev/null +++ b/contrib/gopher_fixes @@ -0,0 +1,129 @@ +9a10 +> #define ROOT_MENU "Root Menu" +11d11 +< +149a150,165 +> /* /gopher-data/ hack by Ian C Blenke */ +> if(ar[i]->host[0]) { +> char host[MAX_STRING_LEN]; +> +> HOSTNAME(host,MAX_STRING_LEN); +> if(strncmp(ar[i]->host, host, strlen(host))==0) +> /* problem: if we fall through here we may get // */ +> if(ar[i]->path[0]) { +> fprintf(fd, +> "<LI> <A NAME=%d HREF=\"/gopher-data%s\"", +> lnum++, (ar[i]->path)+1); +> fprintf(fd,">%s</A>%c",ar[i]->name,LINEFEED); +> break; +> } /* fall through if not type 0 or 1 */ +> } +> +291a308,359 +> /* GN - Patch #1 - for looks - Ian C. Blenke */ +> void get_link_name_from_dir(char *dir, char *munge, char *return_name) +> { +> char munged_name[MAX_STRING_LEN]; +> int i, len, count; +> char dir_name[MAX_STRING_LEN]; +> gopher_ent head; +> gopher_ent *p, *r; +> +> /* strip out the actual parent path name */ +> len=strlen(dir); +> while(len>0) +> { +> if(dir[len]=='/') break; +> len--; +> } +> strncpy(dir_name, dir, len); +> dir_name[len]='\0'; +> /* strip out the virtual parent path name */ +> strcpy(munged_name, munge+GOPHER_LOC_LEN); +> len=strlen(munged_name); +> if((len==0)||(len==1)) +> { +> strcpy(return_name, ROOT_MENU); +> return; +> } +> while(len>1) +> { +> if(munged_name[len]=='/') break; +> len--; +> } +> munged_name[len-1]='\0'; +> strcpy(munged_name, munged_name+len); +> +> /* parse the parent menu file */ +> head.next=NULL; +> count=add_links_from_file(dir_name, "menu", &head); +> p=&head; +> +> /* loop through the chain */ +> for(i=0; i<count; i++) +> { +> if(strcmp((p->path)+2, munged_name)==0) +> strcpy(return_name, p->name); +> r=p->next; +> free(p); +> p=r; +> if(p==NULL) break; +> } +> return; +> } +> +293,294c361 +< DIR *d; +< struct DIR_TYPE *dstruct; +--- +> FILE *fmenu; +298a366,367 +> char menu_name[MAX_STRING_LEN]; +> char parent_name[MAX_STRING_LEN]; +305a375 +> make_full_path(name, "menu", menu_name); +307,308c377,378 +< if(!(d=opendir(name))) +< client_error(fd,BAD_FILE); +--- +> if(!(fmenu=fopen(menu_name, "r"))) +> client_error(fd, BAD_FILE); +310,311c380 +< while(dstruct=readdir(d)) { +< char *fn; +--- +> fclose(fmenu); +313,315c382 +< fn=dstruct->d_name; +< if(ignore(fn)) +< continue; +--- +> num_entries=add_links_from_file(name, "menu", &head); +317,329c384,391 +< if(fn[0]=='.') { +< if(!strcmp(fn,".cap")) +< cap_file=1; +< else +< num_entries+=add_links_from_file(name,fn,&head); +< } +< else { +< add_local_link(fn,&head); +< num_entries++; +< } +< } +< fprintf(fd,"<TITLE> Gopher Index of %s</TITLE>%c",unmunged_name,LINEFEED); +< fprintf(fd,"<H1>Gopher Index of %s</H1>%c",unmunged_name,LINEFEED); +--- +> get_link_name_from_dir(name, unmunged_name, parent_name); +> fprintf(fd,"<HEADER>%c",LINEFEED); +> fprintf(fd,"<TITLE> %s </TITLE>%c",parent_name,LINEFEED); +> fprintf(fd,"<BASE HREF=\"http://su102w.ess.harris.com\">%c", LINEFEED); +> fprintf(fd,"</HEADER>%c",LINEFEED); +> fprintf(fd,"<BODY>%c", LINEFEED); +> fprintf(fd,"<H1>Harris ESS InfoServer Gopher</H1>%c",LINEFEED); +> fprintf(fd,"<H2> %s </H2>%c", parent_name, LINEFEED); +338c400 +< closedir(d); +--- +> fprintf(fd,"</BODY>%c", LINEFEED); + diff --git a/contrib/listen.c b/contrib/listen.c new file mode 100644 index 0000000..4d6fbb4 --- /dev/null +++ b/contrib/listen.c @@ -0,0 +1,340 @@ +/* + * Startup program for netrek. Listens for connections, then forks off + * servers. Based on code written by Brett McCoy, but heavily modified. + * + * To compile, first change the defines for SERVER, LOGFILE, DEF_PORT to what + * you want. Then use: + * + * cc -O listen.c -o listen + * + * 'listen' only accepts one option, '-p' to override the default port to + * listen on. Use it like: 'listen -p 2592'. + * + * Note that descriptor 2 is duped to descriptor 1, so that stdout and + * stderr go to the same file. + */ + + + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <netdb.h> +#include <errno.h> +#include <time.h> +#include <netinet/in.h> +#include <fcntl.h> +#include <varargs.h> +#include <stdio.h> +#include <signal.h> +#include <string.h> + +#define SERVER "/stg1/rob/netrek/lib/ntserv" /* filename of server */ +#define LOGFILE "/dev/null" /* filename of log file */ +#define DEF_PORT 2592 /* port to listen on */ + +int listenSock; +short port = DEF_PORT; +char *program; + +char * dateTime(); +void detach(); +void getConnections(); +void getListenSock(); +void multClose(); +void reaper(); +void terminate(); + +/* + * Error reporting functions ripped from my library. + */ +void syserr(); +void warnerr(); +void fatlerr(); +void err(); +char *lasterr(); + +struct hostent * gethostbyaddr(); +char * inet_ntoa(); + + + +main(argc, argv) +int argc; +char *argv[]; +{ + int i; + + for (i = 1; i < argc; i++) { + + if (argv[i][0] == '-') { + if (strcmp(argv[i]+1, "p") == 0) { + port = atoi(argv[i+1]); + } else { + fatlerr(1, 0, "unrecognized option: %s.", argv[i]); + } + } + } + + program = argv[0]; /* let err functions know our name */ + detach(); /* detach from terminal, close files, etc. */ + getListenSock(); + signal(SIGCHLD, reaper); + signal(SIGTERM, terminate); + + while (1) { + getConnections(); + } +} + +/*********************************************************************** + * Detach process in various ways. + */ + +void +detach() { + int fd, rc, mode; + + mode = S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR; + if ((fd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, mode)) == -1) + syserr(1, "detach", "couldn't open log file. [%s]", dateTime()); + dup2(fd, 2); + dup2(fd, 1); + multClose(1, 2, -1); /* close all other file descriptors */ + warnerr(0, "started at %s on port %d.", dateTime(), port); + + /* fork once to escape the shells job control */ + if ((rc = fork()) > 0) + exit(0); + else if (rc < 0) + syserr(1, "detach", "couldn't fork. [%s]", dateTime()); + + /* now detach from the controlling terminal */ + if ((fd = open("/dev/tty", O_RDWR, 0)) == -1) { + warnerr("detach", "couldn't open tty, assuming still okay. [%s]", + dateTime()); + return; + } + ioctl(fd, TIOCNOTTY, 0); + close(fd); + setsid(); /* make us a new process group/session */ +} + +/*********************************************************************** + */ + +void +getListenSock() { + struct sockaddr_in addr; + + if ((listenSock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + syserr(1, "getListenSock", "can't create listen socket. [%s]", + dateTime()); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + if (bind(listenSock, (struct sockaddr *) &addr, sizeof(addr)) < 0) + syserr(1, "getListenSock", "can't bind listen socket. [%s]", + dateTime()); + + if (listen(listenSock, 5) != 0) + syserr(1, "getListenSock", "can't listen to socket. [%s]", dateTime()); +} + +/*********************************************************************** + */ + +void +getConnections() { + int len, sock, pid; + struct sockaddr_in addr; + struct hostent *he; + char host[100]; + + len = sizeof(addr); + while ((sock = accept(listenSock, (struct sockaddr *) &addr, &len)) < 0) { + /* if we got interrupted by a dying child, just try again */ + if (errno == EINTR) + continue; + else + syserr(1, "getConnections", + "accept() on listen socket failed. [%s]", dateTime()); + } + + /* get the host name */ + he = gethostbyaddr((char *) &addr.sin_addr.s_addr, + sizeof(addr.sin_addr.s_addr), AF_INET); + if (he != 0) + strcpy(host, he->h_name); + else + strcpy(host, inet_ntoa((u_long) &addr.sin_addr)); + warnerr(0, "connection from %s. [%s]", host, dateTime()); + + /* fork off a server */ + if ((pid = fork()) == 0) { + dup2(sock, 0); + multClose(0, 1, 2, -1); /* close everything else */ + if (execl(SERVER, SERVER, host, 0) != 0) + syserr(1, "getConnections", "couldn't execl %s as the server. [%s]", + SERVER, dateTime()); + } else if (pid < 0) + syserr(1, "getConnections", "can't fork. [%s]", dateTime()); + + close(sock); +} + +/*********************************************************************** + * Returns a string containing the date and time. String area is static + * and reused. + */ + +char * +dateTime() { + time_t t; + char *s; + + time(&t); + s = ctime(&t); + s[24] = '\0'; /* wipe-out the newline */ + return s; +} + +/*********************************************************************** + * Handler for SIGTERM. Closes and shutdowns everything. + */ + +void +terminate() { + int s; + + fatlerr(1, "terminate", "killed. [%s]", dateTime()); + + /* shutdown and close everything */ + for (s = getdtablesize(); s >= 0; s--) { + shutdown(s, 2); + close(s); + } +} + +/*********************************************************************** + * Waits on zombie children. + */ + +void +reaper() { + while (wait3(0, WNOHANG, 0) > 0); +} + +/*********************************************************************** + * Close all file descriptors except the ones specified in the argument list. + * The list of file descriptors is terminated with -1 as the last arg. + */ + +void +multClose(va_alist) +va_dcl +{ + va_list args; + int fds[100], nfds, fd, ts, i, j; + + /* get all descriptors to be saved into the array fds */ + va_start(args); + for (nfds = 0; nfds < 99; nfds++) { + if ((fd = va_arg(args, int)) == -1) + break; + else + fds[nfds] = fd; + } + + ts = getdtablesize(); + + /* close all descriptors, but first check the fds array to see if this + * one is an exception */ + for (i = 0; i < ts; i++) { + for (j = 0; j < nfds; j++) + if (i == fds[j]) break; + if (j == nfds) close(i); + } +} + +/*********************************************************************** + * Error reporting functions taken from my library. + */ + +extern int sys_nerr; +extern char *sys_errlist[]; +extern int errno; + +void +syserr(va_alist) +va_dcl +{ + va_list args; + int rc; + + va_start(args); + rc = va_arg(args, int); + err(args); + if (errno < sys_nerr) + fprintf(stderr, " system message: %s\n", sys_errlist[errno]); + + exit(rc); +} + +void +warnerr(va_alist) +va_dcl +{ + va_list args; + + va_start(args); + err(args); +} + +void +fatlerr(va_alist) +va_dcl +{ + va_list args; + int rc; + + va_start(args); + rc = va_arg(args, int); + err(args); + + exit(rc); +} + +void +err(args) +va_list args; +{ + + char *func, *fmt; + + if (program != 0) + fprintf(stderr, "%s", program); + func = va_arg(args, char *); + if (func != 0 && strcmp(func, "") != 0) + fprintf(stderr, "(%s)", func); + fprintf(stderr, ": "); + + fmt = va_arg(args, char *); + vfprintf(stderr, fmt, args); + fputc('\n', stderr); + fflush(stderr); +} + +char * +lasterr() { + if (errno < sys_nerr) + return sys_errlist[errno]; + else + return "No message text for this error."; +} + diff --git a/contrib/standalone.c b/contrib/standalone.c new file mode 100644 index 0000000..69c5f60 --- /dev/null +++ b/contrib/standalone.c @@ -0,0 +1,224 @@ +/* Main program for httpd - allows operation as a standalone program + * without running from inetd or as an inetd child. + */ +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <string.h> +#include <signal.h> + +extern int getopt(int argc, char ** argv, const char * optstring); +extern char *optarg; +extern int optind, opterr; + +#include "httpd.h" + +void +DoNothing() +{ + return; +} + +void +SetAlarm() +{ + alarm(10); +} + +int +main(int argc, char ** argv) +{ + int srvsock; + struct sockaddr_in iso; + int srvport; + char * progname; + char * config_file=CONFIG_FILE; + char * portname = "http"; + int standalone = 0; + int inetd = 0; + int badarg = 0; + int c; + char * errptr; + struct sigaction sa; + + memset((void *)&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = DoNothing; + sigfillset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGCLD, &sa, NULL); + sa.sa_handler = SetAlarm; + sigaction(SIGALRM, &sa, NULL); + alarm(10); + errno = 0; + progname = strrchr(argv[0], '/'); + if (progname == NULL) { + progname = argv[0]; + } else { + ++progname; + } + while ((c = getopt(argc, argv, "Hf:p:si")) != EOF) { + switch(c){ + case 'f': + config_file = optarg; + break; + case 'H': + fprintf(stderr,"\ +%s - a simple http daemon\n\ +Usage: %s [-H] [-f file] [-p port] [-s] [-i]\n\ +\n\ +-H print this message and exit\n\ +-f file specify name of config file (default is %s)\n\ +-p port specify symbolic or numeric port to listen on (default is http)\n\ +-s act as a standalone server rather than run under inetd\n\ +-i assume startup was from inetd.\n\ +\n\ +Note that one of the -i or -s options must be given.\n", + progname, progname, CONFIG_FILE); + exit(0); + break; + case 'p': + portname = optarg; + break; + case 's': + standalone = 1; + inetd = 0; + break; + case 'i': + inetd = 1; + standalone = 0; + break; + default: + badarg = 1; + break; + } + } + argc -= optind; + argv += optind; + if (argc > 0) { + badarg = 1; + } + if (badarg) { + fprintf(stderr, "%s: incorrect arguments, use -H option for help.\n", + progname); + exit(2); + } + if (standalone == 0 && inetd == 0) { + fprintf(stderr, + "%s: must give one of -i or -s options (see -H for help).\n", + progname); + exit(2); + } + if (inetd) { + /* If this is supposed to run from inetd, just start processing requests. + */ + read_config(config_file,stdout); + process_request(stdin,stdout); + + fclose(stdin); + fclose(stdout); + exit(0); + } + + /* This must be a standalone server - do the inetd work myself, forking + * off a child to handle each new connection. + */ + + srvport = (int)strtol(portname, &errptr, 10); + if (errptr == NULL || errptr == portname || *errptr != '\0') { + struct servent *sp; + sp = getservbyname(portname, "tcp"); + if (sp == NULL) { + fprintf(stderr, "%s: unable to lookup service \"%s\".\n", + progname, portname); + exit(2); + } + srvport = sp->s_port; + } + srvsock = socket(AF_INET, SOCK_STREAM, 0); + if (srvsock < 0) { + fprintf(stderr, "%s: socket() call failed with errno %d (%s)\n", + progname, errno, strerror(errno)); + exit(2); + } + memset(&iso, 0, sizeof(iso)); + iso.sin_family = AF_INET; + iso.sin_addr.s_addr = INADDR_ANY; + iso.sin_port = srvport; + errno = 0; + if (bind(srvsock, (struct sockaddr *)&iso, sizeof(iso)) == -1) { + fprintf(stderr, "%s: bind() call failed with errno %d (%s)\n", + progname, errno, strerror(errno)); + exit(2); + } + errno = 0; + if (listen(srvsock, 5) == -1) { + fprintf(stderr, "%s: listen() call failed with errno %d (%s)\n", + progname, errno, strerror(errno)); + exit(2); + } + for ( ; ; ) { + struct sockaddr_in fromaddr; + int fromlen; + int newfd; + int childpid; + + memset(&fromaddr, 0, sizeof(fromaddr)); + fromaddr.sin_family = AF_INET; + fromlen = sizeof(fromaddr); + errno = 0; + newfd = accept(srvsock, (struct sockaddr *)&fromaddr, &fromlen); + if (newfd < 0) { + if (errno == EINTR) { + /* This must have been a SIGCLD or SIGALRM awakening us from a + * deep slumber. Just reap children to get rid of zombies and + * loop again. + */ + int stat; + int rval; + + while ((rval = waitpid((pid_t)-1, &stat, WNOHANG)) > 0); + } else { + fprintf(stderr, "%s: accept() call failed with errno %d (%s)\n", + progname, errno, strerror(errno)); + exit(2); + } + } else { + childpid = fork(); + if (childpid < 0) { + fprintf(stderr, "%s: fork() call failed with errno %d (%s)\n", + progname, errno, strerror(errno)); + exit(2); + } else if (childpid == 0) { + /* This is the child. Set stdin and stderr to newfd and + * process httpd type stuff. Alos clear any alarms and + * signal handlers. + */ + alarm(0); + sa.sa_handler = SIG_DFL; + sigaction(SIGALRM, &sa, NULL); + sigaction(SIGCLD, &sa, NULL); + close(0); + close(1); + dup2(newfd,0); + dup2(newfd,1); + close(newfd); + setvbuf(stdout, NULL, _IOLBF, BUFSIZ); + read_config(config_file,stderr); + process_request(stdin,stdout); + fclose(stdin); + fclose(stdout); + exit(0); + } else { + /* In parent, just close the new socket and listen again + */ + close(newfd); + } + } + } +} diff --git a/errors/bad_ann_request.html b/errors/bad_ann_request.html new file mode 100644 index 0000000..5b08767 --- /dev/null +++ b/errors/bad_ann_request.html @@ -0,0 +1,5 @@ +<TITLE>Annotation error</TITLE> +<H1>Bad Annotation Request</H1> + + +Your World Wide Web browser sent an annotation request which this server was unable to understand. diff --git a/errors/bad_ann_server.html b/errors/bad_ann_server.html new file mode 100644 index 0000000..c981c77 --- /dev/null +++ b/errors/bad_ann_server.html @@ -0,0 +1,6 @@ +<TITLE>Misconfigured Annotation Server</TITLE> +<H1>Misconfigured Annotation Server</H1> + + +This annotation server has not been set up properly or has been corrupted. +Please contact the administrators to inform them of this fact. diff --git a/errors/bad_file.html b/errors/bad_file.html new file mode 100644 index 0000000..50784c8 --- /dev/null +++ b/errors/bad_file.html @@ -0,0 +1,7 @@ +<TITLE>Bad File Request</TITLE> + +<H1>Bad File Request</H1> + + +Your browser attempted to access a file which doesn't exist on this +server, or a file for which it does not have permission to read. diff --git a/errors/bad_request.html b/errors/bad_request.html new file mode 100644 index 0000000..4512395 --- /dev/null +++ b/errors/bad_request.html @@ -0,0 +1,6 @@ +<TITLE>Browser error</TITLE> +<H1>Bad Request</H1> + + +Your World Wide Web browser sent a request which this server was unable +to understand. diff --git a/errors/bad_server.html b/errors/bad_server.html new file mode 100644 index 0000000..a039bdd --- /dev/null +++ b/errors/bad_server.html @@ -0,0 +1,6 @@ +<TITLE>Misconfigured Server</TITLE> +<H1>Misconfigured Server</H1> + + +This server has not been set up properly by the maintainers of this node. + Please contact the administrators to inform them of this fact. diff --git a/errors/gopher_error.html b/errors/gopher_error.html new file mode 100644 index 0000000..b4c92b8 --- /dev/null +++ b/errors/gopher_error.html @@ -0,0 +1,8 @@ +<TITLE>Gopher Setup Error</TITLE> +<H1>Gopher Setup Error</H1> + +This server had a problem reading a gopher directory. Please contact its +administrators. <P> + + + diff --git a/errors/unimplemented.html b/errors/unimplemented.html new file mode 100644 index 0000000..b6a047d --- /dev/null +++ b/errors/unimplemented.html @@ -0,0 +1,7 @@ +<TITLE>Unimplemented Request</TITLE> +<H1>Unimplemented Request</H1> + + + +Your browser requested a service which this server does not know how to +resolve. diff --git a/http_config.c b/http_config.c new file mode 100644 index 0000000..8422164 --- /dev/null +++ b/http_config.c @@ -0,0 +1,94 @@ +/* + * http_config.c: auxillary functions for reading httpd's config file + * and converting filenames into a namespace + * + * Rob McCool 3/21/93 + * + */ + +#include "httpd.h" + +typedef struct { + char name[MAX_STRING_LEN]; + char real[MAX_STRING_LEN]; +} alias; + +static int num_aliases; +alias aliases[MAX_ALIASES]; + + +char original_name[MAX_STRING_LEN]; /* !!! FIX THIS !!! */ + +void unmunge_name(char *name) +{ + strcpy(name,original_name); +} + +void translate_name(char *name) +{ + register int x,l; + + strcpy(original_name,name); + + for(x=0;x<num_aliases;x++) { + l=strlen(aliases[x].name); + if(!(strncmp(name,aliases[x].name,l))) + strsubfirst(l,name,aliases[x].real); + } +} + +/* debugging */ +void dump_aliases() +{ + register int x; + char blorf[MAX_STRING_LEN]; + + for(x=0;x<num_aliases;x++) + printf("%s:%s\n",aliases[x].name,aliases[x].real); + strcpy(blorf,"./../.././.././.././../etc/passwd"); + translate_name(blorf); + printf("translated %s to %s\n","./../.././.././.././../etc/passwd",blorf); + strcpy(blorf,"../.././../../../../../././../././etc/passwd"); + translate_name(blorf); +} + +int read_config(char *name, FILE *errors) +{ + FILE *cfg; + char config_line[MAX_STRING_LEN]; + + if(!(cfg=fopen(name,"r"))) + server_error(errors,CONFIG_FILENAME); + + num_aliases=0; + while(fgets(config_line,MAX_STRING_LEN,cfg)) + if((config_line[0] != '\0') && (config_line[0] != '#') && + (config_line[0] != '\n')) { + register int x=0,y,z; + + x=0; + while((config_line[x] != '\0') && (config_line[x] != ':')) + x++; + if(config_line[x] == '\0') + server_error(errors,CONFIG_SYNTAX); + + config_line[x]='\0'; + strcpy(aliases[num_aliases].name,config_line); + + y=x+1; + if(config_line[y]=='\0') server_error(errors,CONFIG_SYNTAX); + + while(config_line[y] != '\0') y++; + if(config_line[y-1]=='\n') + config_line[--y]='\0'; + strcpy_nocrlf(aliases[num_aliases].real,&config_line[x+1]); + + if((x==1) && (config_line[y-1] != '/')) { + aliases[num_aliases].real[y-2]='/'; /* y-x-1 */ + aliases[num_aliases].real[y]='\0'; /* y-x-1 +1 */ + } + translate_name(aliases[num_aliases].name); + num_aliases++; + } +} + diff --git a/http_dir.c b/http_dir.c new file mode 100644 index 0000000..284273b --- /dev/null +++ b/http_dir.c @@ -0,0 +1,125 @@ +/* + * http_dir.c: Handles the on-the-fly html index generation + * + * Rob McCool + * 3/23/93 + * + */ + + +/* httpd.h includes proper directory file */ +#include "httpd.h" + +void output_directories(char **ar,int n,char *name,FILE *fd) +{ + int x,lnum=0; + + if(!strcmp(name,"/")) name[0]='\0'; + for(x=0;x<n;x++) { + if(strcmp(ar[x],".")) { + fprintf(fd,"<LI> <A NAME=%d HREF=\"",++lnum); + if(strcmp(ar[x],"..")) + { + if (name[strlen(name)-1] == '/') + fprintf(fd,"%s%s\">%s</A>",name,ar[x],ar[x]); + else + fprintf(fd,"%s/%s\">%s</A>",name,ar[x],ar[x]); + } + else { + char blorf[MAX_STRING_LEN]; + strcpy(blorf,name); + if (name[strlen(name)-1] == '/') + strcat(blorf,".."); + else + strcat(blorf,"/.."); + getparents(blorf); + if(blorf[0]=='\0') { + blorf[0]='/'; + blorf[1]='\0'; + } + fprintf(fd,"%s\">%s</A>",blorf,"Parent Directory"); + } + fputc(LINEFEED,fd); + } + } +} + +int dsortf(char **s1,char **s2) +{ + return(strcmp(*s1,*s2)); +} + + +void index_directory(char *name, FILE *fd) +{ + DIR *d; + struct DIR_TYPE *dstruct; + int num_ent=0,x; + struct ent *head,*p,*q; + char **ar; + char unmunged_name[MAX_STRING_LEN]; + + strcpy(unmunged_name,name); + unmunge_name(unmunged_name); + +#ifdef GROK_GOPHER + if(!strncmp(unmunged_name,GOPHER_LOCATION,GOPHER_LOC_LEN)) { + gopher_index(name,fd); + return; + } +#endif + + if(!(d=opendir(name))) + client_error(fd,BAD_FILE); + +/* MUST RE-TRANSLATE DIRECTORY NAME SO USER DOES NOT SEE TRUE NAMES */ + strcpy(name,unmunged_name); + +/* Spew HTML preamble */ + fprintf(fd,"<TITLE>Index of %s</TITLE>",name); + fputc(LINEFEED,fd); + fprintf(fd,"<H1>Index of %s</H1>",name); + fputc(LINEFEED,fd); + + fprintf(fd,"<UL>"); + fputc(LINEFEED,fd); + +/* + * Since we don't know how many dir. entries there are, put them into a + * linked list and then arrayificate them so qsort can use them. + */ + head=(struct ent *)malloc(sizeof(struct ent)); + q=head; + while(dstruct=readdir(d)) { + p=(struct ent *)malloc(sizeof(struct ent)); + p->name=strdup(dstruct->d_name); + p->next=NULL; + q->next=p; + q=p; + num_ent++; + } + ar=(char **) malloc(num_ent*sizeof(char *)); + p=head; + x=0; + while(p=p->next) + ar[x++]=p->name; + + qsort((void *)ar,num_ent,sizeof(char *), +#ifdef ULTRIX_BRAIN_DEATH + (int (*))dsortf); +#else + (int (*)(const void *,const void *))dsortf); +#endif + + output_directories(ar,num_ent,name,fd); + free(ar); + p=head->next; + q=head; + while(p=p->next) { + free(q->name); + free(q); + q=p; + } + closedir(d); + fprintf(fd,"</UL>"); +} diff --git a/http_gopher.c b/http_gopher.c new file mode 100644 index 0000000..83d6ed7 --- /dev/null +++ b/http_gopher.c @@ -0,0 +1,341 @@ +/* + * http_gopher.c: groks gopher directories and spits back HTML links + * + * This is big and ugly. + * + * Rob McCool (robm@ncsa.uiuc.edu) + */ + +#include "httpd.h" + + +#ifdef GROK_GOPHER +/* Great green globs of greasy grimy gopher guts... */ + + +typedef struct glist { + char type; + char name[MAX_STRING_LEN]; + int number; + char host[MAX_STRING_LEN]; + int port; + char path[MAX_STRING_LEN]; + struct glist *next; +} gopher_ent; + +/* Number is optional */ +#define PATH 1 +#define TYPE (1<<1) +#define NAME (1<<2) +#define PORT (1<<3) +#define HOST (1<<4) +#define ALL (PATH | TYPE | NAME | PORT | HOST) + +#define NUM_IGNORE 9 + +static char *ignore_files[]= +{ + ".", + "..", + "bin", + "core", + "dev", + "etc", + "home", + "usr", + "lost+found" + }; + +static FILE *fd; + +int ignore(char *name) { + int x; + + for(x=0;x<NUM_IGNORE;x++) + if(!strcmp(ignore_files[x],name)) + return 1; + + return 0; +} + +void process_cap_file(char *name,gopher_ent **ary,int n) { + char path[MAX_STRING_LEN]; + DIR *d;FILE *f; + struct DIR_TYPE *cap; + int l,i; + + make_full_path(name,".cap",path); + l=strlen(path); + if(!(d=opendir(path))) + return; + + path[l++]='/'; + while(cap=readdir(d)) { + strcpy(&path[l],cap->d_name); + if(!is_directory(path)) { + if(f=fopen(path,"r")) { + while(!feof(f)) { + char line[MAX_STRING_LEN]; + fgets(line,MAX_STRING_LEN,f); + if(!strncmp(line,"Name=",5)) { + for(i=0;i<n;i++) { + if(!strcmp(ary[i]->path,cap->d_name)) { + strcpy_nocrlf(ary[i]->name,&line[5]); + break; + } + } + } + if(!strncmp(line,"Numb=",5)) { + for(i=0;i<n;i++) { + if(!strcmp(ary[i]->path,cap->d_name)) { + ary[i]->number=atoi(&line[5]); + break; + } + } + } + } + fclose(f); + } + } + } + closedir(d); +} + +int gsortf(gopher_ent **g1,gopher_ent **g2) { + register int i1=(*g1)->number,i2=(*g2)->number; + + if((i1 >= 0) && (i2 >= 0)) { + if(i1 == i2) return 0; + else if(i1 > i2) return 1; + else return -1; + } + else if(i1 >= 0) return -1; + else if(i2 >= 0) return 1; + + else return(strcmp((*g1)->name,(*g2)->name)); +} + +gopher_ent **sort_list(gopher_ent *head,char *name,int n,int cap_file) { + gopher_ent **ret; + gopher_ent *p=head->next; + int i=0; + + if(!(ret=(gopher_ent **)malloc(n*sizeof(gopher_ent *)))) + server_error(fd,MEMORY); + + while(p) { + ret[i++]=p; + p=p->next; + } + if(cap_file) process_cap_file(name,ret,n); + qsort((void *)ret,n,sizeof(gopher_ent *), +#ifdef ULTRIX_BRAIN_DEATH + (int (*))gsortf); +#else + (int (*)(const void *,const void *))gsortf); +#endif + return ret; +} + +void spew_and_free_array(char *name,gopher_ent **ar,int n) { + int lnum=0,i; + + if(name[0]=='/' && !(name[1])) name[0]='\0'; + + + for(i=0;i<n;i++) { + switch(ar[i]->type) { + case '0': + case '1': + case '4': + case '5': + case '6': + case '9': + case 'g': + case 'I': + case 'h': + case 's': + /* THIS NEEDS WORK... */ + if(!strncmp(ar[i]->path,"ftp:",4)) { + int y=4; + + fprintf(fd,"<LI> <A NAME=%d HREF=\"file://",lnum++); + while(ar[i]->path[y]!='@' && ar[i]->path[y]) + fputc(ar[i]->path[y++],fd); + if(ar[i]->path[y]) + while(ar[i]->path[++y]) + fputc(ar[i]->path[y],fd); + else + fputc('/',fd); + fprintf(fd,"\">%s</A>%c",ar[i]->name,LINEFEED); + break; + } + if(ar[i]->host[0]) { + char *host; + + host = full_hostname(); + /* problem: if we fall through here we may get // */ + if(strcmp(host,ar[i]->host)) { + if(ar[i]->path[0]) { + fprintf(fd, + "<LI> <A NAME=%d HREF=\"gopher://%s:%d/%c%s\"", + lnum++,ar[i]->host,ar[i]->port,ar[i]->path[0], + ar[i]->path); + } + else { + fprintf(fd,"<LI> <A NAME=%d HREF=\"gopher://%s:%d/\"", + lnum++,ar[i]->host,ar[i]->port); + } + fprintf(fd,">%s</A>%c",ar[i]->name,LINEFEED); + break; + } + } + /* check the path to see if it starts with 0/ or 1/ */ + if((ar[i]->path[0] == '0') && (ar[i]->path[1] == '/')) + strsubfirst(2,ar[i]->path,""); + else if((ar[i]->path[0] == '1') && (ar[i]->path[1] == '/')) + strsubfirst(2,ar[i]->path,""); + fprintf(fd,"<LI> <A NAME=%d HREF=\"%s/%s\">%s</A>%c", + lnum++,name,ar[i]->path,ar[i]->name,LINEFEED); + break; + case '8': + fprintf(fd,"<LI> <A NAME=%d HREF=\"telnet://%s:%d/\">%s</A>%c", + lnum++,ar[i]->host,ar[i]->port,ar[i]->name,LINEFEED); + break; + case 'T': + fprintf(fd,"<LI> <A NAME=%d HREF=\"tn3270://%s:%d/\">%s</A>%c", + lnum++,ar[i]->host,ar[i]->port,ar[i]->name,LINEFEED); + break; + } + free(ar[i]); + } + free(ar); +} + +gopher_ent *add_local_link(char *name, gopher_ent *head) { + gopher_ent *p; + + if(!(p=(gopher_ent *)malloc(sizeof(gopher_ent)))) + server_error(fd,MEMORY); + p->type='0'; + p->number=-1; + strcpy(p->path,name); + strcpy(p->name,name); + p->host[0]='\0'; + p->port=-1; + p->next=head->next; + head->next=p; +} + +int add_links_from_file(char *dir,char *name,gopher_ent *head) { + char tstr[MAX_STRING_LEN]; + FILE *f; + gopher_ent *p; + int n=0; + + make_full_path(dir,name,tstr); + /* Gopher is stupid. */ + if(!(f=fopen(tstr,"r"))) { + return; + } + + while(!feof(f)) { + int g; + + if(!(p=(gopher_ent *)malloc(sizeof(gopher_ent)))) + server_error(fd,MEMORY); + p->number=-1; + g=0; + while(g!=ALL) { + if(feof(f)) { + free(p); + p=NULL; + break; + } + fgets(tstr,MAX_STRING_LEN,f); + if(!strncmp(tstr,"Name=",5)) { + strcpy_nocrlf(p->name,&tstr[5]); + g|=NAME; + } + else if(!strncmp(tstr,"Host=",5)) { + strcpy_nocrlf(p->host,&tstr[5]); + g|=HOST; + } + else if(!strncmp(tstr,"Path=",5)) { + strcpy_nocrlf(p->path,&tstr[5]); + g|=PATH; + } + else if(!strncmp(tstr,"Port=",5)) { + if(tstr[5]) + sscanf(&tstr[5],"%d",&p->port); + else p->port=-1; + g|=PORT; + } + else if(!strncmp(tstr,"Type=",5)) { + p->type=tstr[5]; + g|=TYPE; + } + else if(!strncmp(tstr,"Numb=",5)) { + if(tstr[5]) + sscanf(&tstr[5],"%d",&p->number); + } + } + if(p) { + p->next=head->next; + head->next=p; + n++; + } + } + return(n); +} + +void gopher_index(char *name, FILE *outfile) { + DIR *d; + struct DIR_TYPE *dstruct; + gopher_ent head; + gopher_ent **sorted; + int num_entries; + char unmunged_name[MAX_STRING_LEN]; + int cap_file=0; + + fd=outfile; + head.next=NULL; + + strcpy(unmunged_name,name); + unmunge_name(unmunged_name); + + if(!(d=opendir(name))) + client_error(fd,BAD_FILE); + + while(dstruct=readdir(d)) { + char *fn; + + fn=dstruct->d_name; + if(ignore(fn)) + continue; + + if(fn[0]=='.') { + if(!strcmp(fn,".cap")) + cap_file=1; + else + num_entries+=add_links_from_file(name,fn,&head); + } + else { + add_local_link(fn,&head); + num_entries++; + } + } + fprintf(fd,"<TITLE> Gopher Index of %s</TITLE>%c",unmunged_name,LINEFEED); + fprintf(fd,"<H1>Gopher Index of %s</H1>%c",unmunged_name,LINEFEED); + fprintf(fd,"<UL>%c",LINEFEED); + + if(num_entries) { + sorted=sort_list(&head,name,num_entries,cap_file); + spew_and_free_array(unmunged_name,sorted,num_entries); + } + + fprintf(fd,"</UL>%c",LINEFEED); + closedir(d); +} + +#endif diff --git a/http_request.c b/http_request.c new file mode 100644 index 0000000..a79b3cd --- /dev/null +++ b/http_request.c @@ -0,0 +1,303 @@ +/* + * http_request.c: functions to get and process requests + * + * Rob McCool 3/21/93 + * + */ + + +#include "httpd.h" +#ifdef LOGGING +#include <arpa/inet.h> /* for inet_ntoa */ +#include <time.h> /* for ctime */ +#endif /* LOGGING */ + +#ifdef ANNOTATIONS +#define COMMANDS 5 +#else +#define COMMANDS 1 +#endif + +static char *commands[]={ + "GET ", +#ifdef ANNOTATIONS + "ANN_GET ", + "ANN_SET ", + "ANN_CHANGE ", + "ANN_DELETE ", +#endif + }; + +#ifdef OLD +void send_fd(FILE *f, FILE *fd) +{ + int num_chars=0; + + while(!feof(f)) { + char c; + + c=fgetc(f); + +#ifdef TRUNCATE_LEN + ++num_chars; + + if((c==LINEFEED) || (c==CRETURN) || (num_chars==TRUNCATE_LEN)) { + fputc(LINEFEED,fd); + + if(c==CRETURN && !feof(f)) { + ++num_chars; + c=fgetc(f); + if(c!=LINEFEED) + fputc(c,fd); + } + else if(num_chars==TRUNCATE_LEN) { + while((c!=LINEFEED) && !feof(f)) + c=fgetc(f); + } + num_chars=0; + } + else +#endif + fputc(c,fd); + } +} +#endif + +void send_fd(FILE *f, FILE *fd) +{ + int num_chars=0; + + while (1) + { + char c; + + c = fgetc(f); + if (feof(f)) + goto done; + +#ifdef TRUNCATE_LEN + ++num_chars; + + if((c==LINEFEED) || (c==CRETURN) || (num_chars==TRUNCATE_LEN)) { + fputc(LINEFEED,fd); + + if(c==CRETURN && !feof(f)) { + ++num_chars; + c=fgetc(f); + if(c!=LINEFEED) + fputc(c,fd); + } + else if(num_chars==TRUNCATE_LEN) { + while((c!=LINEFEED) && !feof(f)) + c=fgetc(f); + } + num_chars=0; + } + else +#endif + fputc(c,fd); + } + + done: + return; +} + +void send_file(char *file, FILE *fd) { + FILE *f; + + if(!(f=fopen(file,"r"))) + client_error(fd,BAD_FILE); + + send_fd(f,fd); +} + +void send_error_file(char *file,FILE *fd) { + FILE *f; + + if(!(f=fopen(file,"r"))) { + fprintf(fd,"<TITLE>Bad configuration</TITLE>%c",LINEFEED); + fprintf(fd,"<H1>Bad configuration</H1>%c",LINEFEED); + fprintf(fd, + "%cThis server cannot find an error file to send you.<P>%c", + LINEFEED,LINEFEED); + exit(1); + } + send_fd(f,fd); +} + + +/* GET */ +void send_node(char *file,FILE *fd) +{ + struct stat finfo; + + if(stat(file,&finfo) == -1) + client_error(fd,BAD_FILE); + + if(S_ISDIR(finfo.st_mode)) { + char name[MAX_STRING_LEN]; + + strcpy(name,file); + /* Bugfix in case there's no trailing slash. */ + if (name[strlen(name)-1] != '/') + strcat(name, "/"); + strcat(name,HTML_DIR_CONTENT); + if(stat(name,&finfo) == -1) + /* index file not found; get dir. list and send it back as HTML */ + index_directory(file,fd); + else send_file(name,fd); + } + + else if(S_ISREG(finfo.st_mode)) + send_file(file,fd); + + else client_error(fd,BAD_FILE); +} + + + +void process_request(FILE *in, FILE *out) +{ + char cmd_line[MAX_STRING_LEN],*ret; + register int x,y,z; +#ifdef LOGGING + char *who_called = NULL; + struct sockaddr addr; + int len, retval; + FILE *fp; + time_t time_val = time(NULL); + char *time_string = ctime(&time_val); +#endif /* LOGGING */ + + for (x=0; x<MAX_STRING_LEN; x++) + { + cmd_line[x] = (char)fgetc(in); + if ((cmd_line[x] == ' ')||(cmd_line[x] == LINEFEED)) + { + break; + } + } + if ((x == MAX_STRING_LEN)||(cmd_line[x] == LINEFEED)) + { + client_error(out,BAD_REQUEST); + } + cmd_line[x + 1] = '\0'; + +/* Currently, this does not take into account execution of different cmds */ + for(y=0;y<COMMANDS;y++) { + if(!(strncmp(commands[y],cmd_line,strlen(commands[y])))) { + int val; + + val = fgetc(in); + if (val != '/') + { + client_error(out,BAD_FILE); /* FIX */ + } + +#ifdef ANNOTATIONS + switch(y) { + case 0: /* GET */ + break; + case 1: /* ANN_GET */ + Get_request(in, out); + return; + break; + case 2: /* ANN_SET */ + Set_request(in, out); + return; + break; + case 3: /* ANN_CHANGE */ + Change_request(in, out); + return; + break; + case 4: /* ANN_DELETE */ + Delete_request(in, out); + return; + break; + } +#endif + + if(!(ret=(char *)malloc(MAX_STRING_LEN*sizeof(char)))) + server_error(out,MEMORY); + + x++; + cmd_line[x] = '/'; + z = x; + x++; + val = fgetc(in); + while ((x < (MAX_STRING_LEN - 1))&&(val != EOF)&& + (val != LINEFEED)&&(val != CRETURN)&&(!isspace(val))) + { + cmd_line[x] = (char)val; + x++; + val = fgetc(in); + } + cmd_line[x] = '\0'; + +#ifdef LOGGING + /* + * Get a sockaddr structure filled in for our peer. + */ + len = sizeof(struct sockaddr); + retval = getpeername(fileno(stdin), &addr, &len); + if (retval == 0) + { + struct in_addr *iaddr; + struct hostent *hptr; + + iaddr = &(((struct sockaddr_in *)&addr)->sin_addr); + hptr = gethostbyaddr((char *)iaddr, + sizeof(struct in_addr), AF_INET); + if (hptr != NULL) + { + who_called = (char *)malloc(strlen(hptr->h_name) + 1); + strcpy(who_called, hptr->h_name); + } + else + { + char *iname; + + iname = inet_ntoa(*iaddr); + if (iname != NULL) + { + who_called = (char *)malloc(strlen(iname) + 1); + strcpy(who_called, iname); + } + else + { + who_called = (char *)malloc( + strlen("UNKNOWN_CALLER") + 1); + strcpy(who_called, "UNKNOWN_CALLER"); + } + } + } + else + { + who_called = (char *)malloc(strlen("UNKNOWN_CALLER") + 1); + strcpy(who_called, "UNKNOWN_CALLER"); + } + + fp = fopen(LOGFILE, "a"); + if (fp) + { + time_string[strlen(time_string) - 1] = '\0'; + fprintf(fp, "%s\t[%s] %s\n", who_called, time_string, cmd_line); + fclose(fp); + } +#endif /* LOGGING */ + + strcpy_nocrlf(ret, &cmd_line[z]); + getparents(ret); + translate_name(ret); + switch(y) { + case 0: /* GET */ + if(ret[0]!='/') client_error(out,BAD_FILE); /* FIX */ + send_node(ret,out); + break; + } + return; + } + } + client_error(out,BAD_REQUEST); +} + diff --git a/httpd.c b/httpd.c new file mode 100644 index 0000000..cca487c --- /dev/null +++ b/httpd.c @@ -0,0 +1,160 @@ +/* + * httpd.c: simple http daemon for answering WWW file requests + * + * + * Rob McCool 3/21/93 + * + */ + + +#include "httpd.h" +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> + +void usage(char *bin) { + fprintf(stderr,"Usage: %s [-s] [-f filename] [-p portnum] [-u userid] [-g groupid]\n",bin); + fprintf(stderr,"-f filename : specify an alternate config file\n"); + fprintf(stderr,"-s : Run as standalone server instead of from inetd\n"); + fprintf(stderr,"-p portnumber : alternate port number.\n"); + fprintf(stderr,"(server must initially run as root for ports under 1024.)\n"); + fprintf(stderr,"\nThe following two options require the daemon to be initially run as root:\n"); + fprintf(stderr,"-u userid : run as named user-id\n"); + fprintf(stderr,"-g groupid : run as named group-id\n"); + fprintf(stderr,"A user or group number may be specified with a # as the first character.\n"); + exit(1); +} + +void detach() { + int x; + + if((x = fork()) > 0) + exit(0); + else if(x == -1) + server_error(stdout,FORK); + +#ifdef BSD + setpgrp(0,getpid()); + if((x=open("/dev/tty",O_RDWR,0)) >= 0) { + ioctl(x,TIOCNOTTY,0); + close(x); + setsid(); + } +#else + setpgrp(); +#endif +} + +#ifdef BSD +void ign() { + int status; + pid_t pid; + + while( (pid = wait3(&status, WNOHANG, NULL)) > 0); +} +#endif + +void standalone_main(int port, uid_t uid, gid_t gid) { + int sd,csd, clen,pid; + struct sockaddr_in sa_server,sa_client; + + detach(); + + if ((sd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1) + server_error(stdout,SOCKET); + + bzero((char *) &sa_server, sizeof(sa_server)); + sa_server.sin_family=AF_INET; + sa_server.sin_addr.s_addr=htonl(INADDR_ANY); + sa_server.sin_port=htons(port); + if(bind(sd,(struct sockaddr *) &sa_server,sizeof(sa_server)) == -1) { + perror("bind"); + server_error(stdout,SOCKET_BIND); + } + listen(sd,5); + +#ifdef BSD + signal(SIGCHLD,(void (*)())ign); +#else + signal(SIGCHLD,SIG_IGN); +#endif + + /* we're finished getting the socket, switch to running userid */ + setuid(uid); + setgid(gid); + while(1) { + retry: + clen=sizeof(sa_client); + if((csd=accept(sd,&sa_client,&clen)) == -1) { + if(errno == EINTR) { +#ifdef BSD + ign(); +#endif + goto retry; + } + perror("accept"); + server_error(stdout,SOCKET_ACCEPT); + } + if((pid = fork()) == -1) + server_error(stdout,FORK); + else if(!pid) { + close(0); + close(1); + dup2(csd,0); + dup2(csd,1); + close(sd); + process_request(stdin,stdout); + fclose(stdin); + fclose(stdout); + exit(0); + } + close(csd); + } +} + +extern char *optarg; +extern int optind; + +main(int argc, char *argv[]) +{ + char config_file[MAX_STRING_LEN]; + int standalone=0,c,port = HTTPD_PORT; + uid_t uid; + gid_t gid; + + uid = uname2id(DEFAULT_USER); + gid = gname2id(DEFAULT_GROUP); + + strcpy(config_file,CONFIG_FILE); + while((c = getopt(argc,argv,"sf:p:u:g:")) != -1) { + switch(c) { + case 's': + standalone=1; + break; + case 'f': + strcpy(config_file,optarg); + break; + case 'p': + port = atoi(optarg); + break; + case 'u': + uid = uname2id(optarg); + break; + case 'g': + gid = gname2id(optarg); + break; + case '?': + usage(argv[0]); + } + } + + read_config(config_file,stdout); + + if(standalone) + standalone_main(port,uid,gid); + else + process_request(stdin,stdout); + fclose(stdin); + fclose(stdout); + exit(0); +} diff --git a/httpd.conf b/httpd.conf new file mode 100644 index 0000000..97a78fd --- /dev/null +++ b/httpd.conf @@ -0,0 +1,31 @@ +# Sample httpd.conf file +# +# Only directories can be mapped. +# +# The format is: +# +# alias:real +# +# where alias is the name that appears to the user, and real is the +# object's real location. Files are specified by an alias with no trailing +# /, and directories need trailing slashes the real name. +# +# Aliases are self-stacking: If you first map something, then map +# something else, the second will be interpreted with all of the previous +# aliases expanded. +# +# To deny permission to a directory, map it to /deny or something +# non-existent. +# +# For instance, to map the root directory to /u/web-docs, you would use the +# line: +# /:/u/web-docs/ +# +# +# After that line, all real directory names will be relative to the new root, +# and all incoming requests will be relative to that new root. +# +# Then, if you want to deny permission to /u/web-docs/private, you would use: +# /private:/deny + +/:/tmp diff --git a/httpd.h b/httpd.h new file mode 100644 index 0000000..6beaf06 --- /dev/null +++ b/httpd.h @@ -0,0 +1,256 @@ +/* + * httpd.h: header for simple (ha! not anymore) http daemon + */ + +/* ----------------------------- config file ------------------------------ */ + +/* Define this to be the default configuration file */ +#define CONFIG_FILE "/usr/local/etc/httpd/httpd.conf" + +/* -------------- Port number for server running standalone --------------- */ + +#define HTTPD_PORT 80 + +/* --------- Default user name and group name running standalone ---------- */ +/* --- These may be specified as numbers by placing a # before a number --- */ + +#define DEFAULT_USER "nobody" +#define DEFAULT_GROUP "#-1" + +/* Define if your system calls are BSD-style */ +#undef BSD + +/* ---------------------------- access logging ---------------------------- */ + +/* If you want to have server access logging, define this. */ +#define LOGGING + +#ifdef LOGGING + +/* The name of the log file -- this must be writeable by the userid + the server is run under (usually 'nobody'). */ +#define LOGFILE "/usr/local/etc/httpd/access_log" + +#endif /* LOGGING */ + +/* ---------------------------- gopher support ---------------------------- */ + +/* Define this if you want to support . links and .cap gopherisms */ +/* This has other effects, see README.GOPHER for details. */ +#undef GROK_GOPHER + +#ifdef GROK_GOPHER + +/* Set this to the port # of your gopher server */ +#define GOPHERPORT 70 + +/* This is the virtual location of the Gopher data directory. You should + establish a mapping from this to your real Gopher data directory in + httpd.conf, as explained in README.GOPHER. */ +#define GOPHER_LOCATION "/gopher-data" +#define GOPHER_LOC_LEN 12 + +#endif /* GROK_GOPHER */ + +/* ----------------------- group annotation support ----------------------- */ + +/* Define this if you want this server to serve as a group annotation + server. See README for details. */ +#undef ANNOTATIONS +/* #define ANNOTATIONS */ + +#ifdef ANNOTATIONS + +/* The physical directory where all group annotations will be stored. + THIS MUST BE WRITEABLE by the user ID under which you run httpd + (usually 'nobody'). */ +#define ANN_DIR "/usr/local/etc/httpd/annotations" + +/* The virtual directory (i.e., from the outside coming into the server) + where the group annotations will be located. + + E.g., if you normally map / to /usr/local/etc/httpd in httpd.conf, + and you have ANN_DIR set to /usr/local/etc/httpd/annotations, then + this should be "/annotations" (note the slash). + + This means that the annotation directory must be browsable + according to the standard rules in httpd.conf. */ +#define ANN_VIRTUAL_DIR "/annotations" + +/* This is your port number, same number used in /etc/services. */ +#define ANN_PORT 80 + +#endif + +/* ------------------------- other customizations ------------------------- */ + +/* Define this if you want errors logged to syslog and your system has the + syslog() system call and syslog.h */ +#undef SYSLOG + +/* The default string lengths */ +#define MAX_STRING_LEN 256 + +/* The maximum number of aliases in the config file */ +#define MAX_ALIASES 20 + +/* Define this to be what your HTML directory content files are called */ +#define HTML_DIR_CONTENT "index.html" + +/* Set this to the hardcoded fully qualified machine name. + + You may #undef this if you choose, and httpd will then use the + gethostname() and gethostbyname() calls to figure out the fully + qualified machine name on the fly; HOWEVER, gethostbyname() called + on the name of the local host coredumps on some systems. */ +/* #define HARDCODED_HOSTNAME "machine.foo.edu" */ +#undef HARDCODED_HOSTNAME + +/* + * The number of characters to truncate the outgoing file's lines to + * "Well behaved" servers should truncate this to 80. Some link pathnames + * tend to go ballistic, so I would set this higher. + * + * If you want to serve JPEG's and other such binary files, #undef this. */ +#undef TRUNCATE_LEN + +/* + * The particular directory style your system supports. If you have dirent.h + * in /usr/include (POSIX) or /usr/include/sys (SYSV), #include + * that file and define DIR_TYPE to be dirent. Otherwise, if you have + * /usr/include/sys/dir.h, define DIR_TYPE to be direct and include that + * file. If you have neither, I'm confused. + */ +#include <dirent.h> +#define DIR_TYPE dirent + +/* ------------------------------ error urls ------------------------------ */ + +/* Error files. BE SURE THESE WORK! NOTE THAT ALIASES ARE NOT TRANSLATED! */ +#define SERVER_ERROR "/usr/local/etc/httpd/errors/bad_server.html" +#define ANN_SERVER_ERROR "/usr/local/etc/httpd/errors/bad_ann_server.html" +#define BAD_REQUEST "/usr/local/etc/httpd/errors/bad_request.html" +#define BAD_ANN_REQUEST "/usr/local/etc/httpd/errors/bad_ann_request.html" +#define BAD_FILE "/usr/local/etc/httpd/errors/bad_file.html" +#define UNIMPLEMENTED "/usr/local/etc/httpd/errors/unimplemented.html" +#define GOPHER_ERROR "/usr/local/etc/httpd/errors/gopher.html" + +/* ----------------------------- other stuff ------------------------------ */ + +/* You shouldn't have to edit anything below this line. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/ioctl.h> + +#ifdef NEXT_BRAIN_DEATH +#define S_ISDIR(m) (((m)&(S_IFMT)) == (S_IFDIR)) +#define S_ISREG(m) (((m)&(S_IFMT)) == (S_IFREG)) +#endif + +#ifdef ultrix +#define ULTRIX_BRAIN_DEATH +#define NEED_STRDUP +#endif + +#ifdef SYSLOG +#include <syslog.h> +#endif + +/* Just in case your linefeed isn't the one the other end is expecting. */ +#define LINEFEED 10 +#define CRETURN 13 + +/* Messages which are logged upon system errors. */ +#define CONFIG_FILENAME \ + "httpd: configuration file not found or permission denied" +#define CONFIG_SYNTAX "httpd: syntax error in configuration file" +#define MEMORY "httpd: ran out of memory" +#define FORK "httpd: could not fork" +#define SOCKET "httpd: socket problem" +#define SOCKET_BIND "httpd: socket bind problem" +#define SOCKET_ACCEPT "httpd: connection accept problem" +#define BAD_USERNAME "httpd: bad username" +#define BAD_GROUPNAME "httpd: bad groupname" + + +#ifdef SYSLOG +#define msg_out(fd,msg) (syslog(LOG_ERR,(msg))) +#else +#if 1 +#define msg_out(fd,msg) fprintf((fd),(msg)) +#else +#define msg_out(fd,msg) (fprintf((fd),(msg)),send_error_file((msg),(fd))) +#endif +#endif + +/* log_error prints a server error to the appropriate places and exits */ +#define server_error(fd,msg) (msg_out(fd,msg),send_error_file(msg,fd),\ + exit(0)) +#define client_error(fd,msg) (send_error_file(msg,fd),exit(0)) + + +struct ent { + char *name; + struct ent *next; +}; + + +typedef struct { + char *url; + int acnt; + int *ann_array; +} LogRec; + +typedef struct { + int hash; + int cnt; + LogRec *logs; +} LogData; + + +/* Function prototypes. */ +extern int read_config(char *name, FILE *errors); +extern void send_node(char *name, FILE *fd); +extern void translate_name(char *name); +extern void process_request(FILE *in, FILE *out); +extern void Get_request(FILE *in, FILE *out); +extern void Set_request(FILE *in, FILE *out); +extern void Change_request(FILE *in, FILE *out); +extern void Delete_request(FILE *in, FILE *out); +extern void strsubfirst(int start,char *dest, char *src); +extern void strcpy_nocrlf(char *dest, char *src); +extern char *strsub(char *src, char *oldstr, char *newstr); +extern void make_full_path(char *src1,char *src2,char *dst); +extern int is_directory(char *name); +extern char *full_hostname(void); +extern void index_directory(char *name, FILE *fd); +extern void getparents(char *name); +extern void gopher_index(char *name,FILE *fd); +extern void send_error_file(char *file,FILE *fd); +extern void send_file(char *file,FILE *fd); +extern void send_error_file(char *file,FILE *fd); +extern LogData *GetLogData(int hash); +extern int FindLogData(LogData *lptr, char *url, int **alist, int *acnt); +extern int AddLogData(LogData *lptr, int indx, char *url); +extern char *HashtoUrl(LogData *lptr, int indx); +extern int WriteLogData(LogData *lptr); +extern void FreeLogData(LogData *lptr); +extern char *WriteAudioData(int hash, int indx, char *data, int len, char *type, int no_change); +extern int WriteAnnData(char *url, int hash, int indx, char *title, char *user, char *date, char *data, int len, int no_change); +extern int ReadAnnData(int hash, int indx, char **title, char **user, char **date); +extern int DeleteLogData(LogData *lptr, int indx); +extern void UnlockIt(void); +extern uid_t uname2id(char *name); +extern gid_t gname2id(char *name); + +#ifdef NEED_STRDUP +extern char *strdup (char *str); +#endif diff --git a/util.c b/util.c new file mode 100644 index 0000000..614b3fe --- /dev/null +++ b/util.c @@ -0,0 +1,167 @@ +/* + * str.c: string utility things + * + * 3/21/93 Rob McCool + * + */ + + +#include <string.h> +#include "httpd.h" +#include <pwd.h> +#include <grp.h> + +void strcpy_nocrlf(char *dest, char *src) +{ + register char c = *src; + + while((c!='\0') && (c!=LINEFEED) && (c!=CRETURN)) + c=(*dest++ = *src++); + + if(c!='\0') *(dest-1)='\0'; +} + +void strsubfirst(int start,char *dest, char *src) +{ + char tmp[MAX_STRING_LEN]; + + strcpy(tmp,&dest[start]); + strcpy(dest,src); + strcpy(&dest[strlen(src)],tmp); +} + +/* + * Parse .. so we don't compromise security + */ +void getparents(char *name) +{ + int l=0,w=0; + const char *lookfor=".."; + + while(name[l]!='\0') { + if(name[l]!=lookfor[w]) (w>0 ? (l-=(w-1),w=0) : l++); + else { + if(lookfor[++w]=='\0') { + if((name[l+1]=='\0') || (name[l+1]=='/')) { + register int m=l+1,n; + + l=l-3; + if(l>=0) { + while((l!=0) && (name[l]!='/')) --l; + } + else l=0; + n=l; + while(name[n]=name[m]) (++n,++m); + w=0; + } + else w=0; + } + else ++l; + } + } +} + +char *strsub(char *src, char *oldstr, char *newstr) { + register char *t=src,*o,*nsrc=src; + int oldlen,newlen; + char *tosender; + + oldlen=strlen(oldstr); + newlen=strlen(newstr); + +/* If the new string is longer, assume the worst: our new string has to + * replace every letter in the source with the new. If not, don't thrash + * malloc */ + if(oldlen<newlen) + nsrc=(char *)malloc(strlen(src)+(newlen-oldlen)*(strlen(src)/oldlen)); + + tosender=nsrc; + o=oldstr; + while(*t) { + if(*t != *o) *nsrc++ = *t++; + else { + register char *tmp=++t; + register int n=oldlen-1; + + ++o; /* advance o (we advanced t above) */ + while((*tmp++ == *o++) && n) { + --n; + } + tmp=newstr; + if(!n) { /* we matched */ + while(*nsrc++ = *tmp++); + t+=oldlen-1;nsrc--; + } + else *nsrc++ = *(t-1); /* we missed, so put the char into new str */ + o=oldstr; /* no matter what, we damaged o */ + } + } + *nsrc='\0'; + /* not calling free is very anti-social */ + if(oldlen<newlen) free(src); + return tosender; +} + +void make_full_path(char *src1,char *src2,char *dst) { + while(*dst++ = *src1++); + *(dst-1)='/'; + while(*dst++ = *src2++); +} + +int is_directory(char *path) { + struct stat finfo; + + if(stat(path,&finfo) == -1) + return 0; /* in error condition, just return no */ + + return(S_ISDIR(finfo.st_mode)); +} + +char *full_hostname (void) +{ +#ifdef HARDCODED_HOSTNAME + return HARDCODED_HOSTNAME; +#else + char str[128]; + int len = 128; + + gethostname (str, len); + return gethostbyname(str)->h_name; +#endif +} + +#ifdef NEED_STRDUP +char *strdup (char *str) +{ + char *dup; + + dup = (char *)malloc (strlen (str) + 1); + dup = strcpy (dup, str); + + return dup; +} +#endif + +uid_t uname2id(char *name) { + struct passwd *ent; + + if(name[0] == '#') + return(atoi(&name[1])); + + if(!(ent = getpwnam(name))) + server_error(stdout,BAD_USERNAME); + + else return(ent->pw_uid); +} + +gid_t gname2id(char *name) { + struct group *ent; + + if(name[0] == '#') + return(atoi(&name[1])); + + if(!(ent = getgrnam(name))) + server_error(stdout,BAD_GROUPNAME); + + else return(ent->gr_gid); +}