/* * tiny.c - a minimal HTTP server that serves static and * dynamic content with the GET method. Neither * robust, secure, nor modular. Use for instructional * purposes only. * Dave O'Hallaron, Carnegie Mellon * * usage: tiny */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* define DEBUGDOT if you want to see the server make progress */ #define DEBUGDOTx /* define DEBUGCGI if you want to see CGI script err msgs on screen */ #define DEBUGCGIx #define BUFSIZE 1024 #define MAXERRS 16 /* externally defined globals */ extern char **environ; /* the environment from libc */ /* * error - wrapper for perror used for bad syscalls */ void error(char *msg) { perror(msg); exit(1); } /* * clienterror - returns an error message to the client */ void clienterror(FILE *stream, char *cause, char *errnum, char *shortmsg, char *longmsg) { fprintf(stream, "HTTP/1.1 %s %s\n", errnum, shortmsg); fprintf(stream, "Content-type: text/html\n"); fprintf(stream, "\n"); fprintf(stream, "Tiny Error"); fprintf(stream, "\n"); fprintf(stream, "%s: %s\n", errnum, shortmsg); fprintf(stream, "

%s: %s\n", longmsg, cause); fprintf(stream, "


The Tiny Web server\n"); } int main(int argc, char **argv) { /* variables for connection management */ int listenfd; /* listening socket */ int connfd; /* connection socked */ int portno; /* port to listen on */ int clientlen; /* byte size of client's address */ int optval; /* flag value for setsockopt */ struct sockaddr_in serveraddr; /* server's addr */ struct sockaddr_in clientaddr; /* client addr */ int requestno; /* how many connections have we recieved? */ /* variables for connection I/O */ FILE *stream; /* stream version of connfd */ char buf[BUFSIZE]; /* message buffer */ char method[BUFSIZE]; /* request method */ char uri[BUFSIZE]; /* request uri */ char version[BUFSIZE]; /* request method */ char filename[BUFSIZE];/* path derived from uri */ char filetype[BUFSIZE];/* path derived from uri */ char cgiargs[BUFSIZE]; /* cgi argument list */ char *p; /* temporary pointer */ int is_static; /* static request? */ struct stat sbuf; /* file status */ int fd; /* static content filedes */ int pid; /* process id from fork */ /* check command line args */ if (argc != 2) { fprintf(stderr, "usage: %s \n", argv[0]); exit(1); } portno = atoi(argv[1]); /* open socket descriptor */ listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) error("ERROR opening socket"); /* allows us to restart server immediately */ optval = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int)); /* bind port to socket */ bzero((char *) &serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons((unsigned short)portno); if (bind(listenfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) error("ERROR on binding"); /* get us ready to accept connection requests */ if (listen(listenfd, 5) < 0) /* allow 5 requests to queue up */ error("ERROR on listen"); /* * main loop: wait for a connection request, parse HTTP, * serve requested content, close connection. */ clientlen = sizeof(clientaddr); requestno = 0; while (1) { /* wait for a connection request */ connfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clientlen); if (connfd < 0) error("ERROR on accept"); #ifdef DEBUGDOT if ((requestno % 50) == 0) printf("\n%6d", requestno); else printf("."); fflush(stdout); #endif requestno++; /* open the child socket descriptor as a stream */ if ((stream = fdopen(connfd, "r+")) == NULL) error("ERROR on fdopen"); /* get the HTTP request line */ fgets(buf, BUFSIZE, stream); sscanf(buf, "%s %s %s\n", method, uri, version); #ifdef DEBUG printf("%d: %s", requestno, buf); #endif /* tiny only supports the GET method */ if (strcasecmp(method, "GET")) { clienterror(stream, method, "501", "Not Implemented", "Tiny does not implement this method"); fclose(stream); continue; } /* read (and ignore) the HTTP headers */ fgets(buf, BUFSIZE, stream); #ifdef DEBUGHDRS printf("%s", buf); #endif while(strcmp(buf, "\r\n")) { fgets(buf, BUFSIZE, stream); #ifdef DEBUGHDRS printf("%s", buf); #endif } /* parse the uri [crufty] */ if (!strstr(uri, "cgi-bin")) { /* static content */ is_static = 1; strcpy(cgiargs, ""); strcpy(filename, "."); strcat(filename, uri); if (uri[strlen(uri)-1] == '/') strcat(filename, "index.html"); } else { /* dynamic content */ is_static = 0; p = index(uri, '?'); if (p) { strcpy(cgiargs, p+1); *p = '\0'; } else { strcpy(cgiargs, ""); } strcpy(filename, "."); strcat(filename, uri); } /* make sure the file exists */ if (stat(filename, &sbuf) < 0) { clienterror(stream, filename, "404", "Not found", "Tiny couldn't find this file"); fclose(stream); continue; } /* serve static content */ if (is_static) { if (strstr(filename, ".html")) strcpy(filetype, "text/html"); else if (strstr(filename, ".gif")) strcpy(filetype, "image/gif"); else if (strstr(filename, ".jpg")) strcpy(filetype, "image/jpg"); else strcpy(filetype, "text/plain"); /* print response header */ fprintf(stream, "HTTP/1.1 200 OK\n"); fprintf(stream, "Server: Tiny Web Server\n"); fprintf(stream, "Content-length: %d\n", (int)sbuf.st_size); fprintf(stream, "Content-type: %s\n", filetype); fprintf(stream, "\r\n"); fflush(stream); /* Use mmap to return arbitrary-sized response body */ if ((fd = open(filename, O_RDONLY)) < 0) error("ERROR in mmap fd open"); if ((p = mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) < 0) error("ERROR in mmap"); fwrite(p, 1, sbuf.st_size, stream); if (munmap(p, sbuf.st_size) < 0) error("ERROR in munmap"); if (close(fd) < 0) error("ERROR in mmap close"); } /* serve dynamic content */ else { /* make sure file is a regular executable file */ if (!(S_IFREG & sbuf.st_mode) || !(S_IXUSR & sbuf.st_mode)) { clienterror(stream, filename, "403", "Forbidden", "You are not allow to access this item"); fclose(stream); continue; } /* print first part of response header */ sprintf(buf, "HTTP/1.1 200 OK\n"); write(connfd, buf, strlen(buf)); sprintf(buf, "Server: Tiny Web Server\n"); write(connfd, buf, strlen(buf)); /* create and run the child CGI process so that all child */ /* output to stdout and stderr goes back to the client via the */ /* connfd socket descriptor */ pid = fork(); if (pid < 0) { error("ERROR in fork"); } else if (pid > 0) { /* parent process */ while (wait(NULL) > 0) ; if (errno != ECHILD) error("ERROR in wait"); } else { /* child process */ /* a real server would set other CGI environ vars as well*/ setenv("QUERY_STRING", cgiargs, 1); close(0); /* close stdin */ dup2(connfd, 1); /* map socket to stdout */ #ifndef DEBUGCGI dup2(connfd, 2); /* map socket to stderr */ #endif if (execve(filename, NULL, environ) < 0) { perror("ERROR in execve"); } } } /* clean up */ fclose(stream); } }