// Based on Molay's webserver /* webserv.c - a minimal web server (version 0.2) * usage: ws portnumber * features: supports the GET command only * runs in the current directory * forks a new child to handle each request * has MAJOR security holes, for demo purposes only * has many other weaknesses, but is a good start Lots of error checking omitted for brevity! */ //#define _XOPEN_SOURCE 600 #include // exit, atoi #include // fopen, fdopen, fclose, fflush, getc, putc, perror, // printf, sscanf,stderr, fgets, FILE #include // stat, S_ISDIR #include // strcmp, strcpy, strchr #include // fork, dup2, close, execl, execlp #include // accept #include // sockaddr_in, htonl, INADDR_ANY, htons //#include // not needed //#include // not needed #include #include #define PORT_NUM 13000 #define LISTENQ 1024 // 2nd argument to listen() int main(int argc, char *argv[]) { void read_til_crnl(FILE *fp); void process_rq( char *rq, int fd ); struct sockaddr_in servaddr; int listenfd, fd; FILE *fpin; char request[BUFSIZ]; // 1. Create the socket listenfd = socket(AF_INET, SOCK_STREAM, 0); fprintf(stderr,"Server socket fd: %d\n", listenfd); // 2. Set up the sockaddr_in // zero it. // bzero(&servaddr, sizeof(servaddr)); // Note bzero is "deprecated". Sigh. memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; // Specify the family servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // use any network card present servaddr.sin_port = htons(PORT_NUM); /* daytime server */ // 3. "Bind" that address object to our listening file descriptor bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); // 4. Tell the system that we are going to use this sockect for // listening and request a queue length listen(listenfd, LISTENQ); /* main loop here */ while(1) { /* take a call and buffer it */ fprintf(stderr,"Waiting for accept\n"); fd = accept(listenfd, NULL, NULL ); fprintf(stderr,"Got a connection\n"); fpin = fdopen(fd, "r" ); // read request fgets(request, BUFSIZ, fpin); printf("got a call: request = %s", request); read_til_crnl(fpin); // ignore everything else till crnl /* do what client asks */ process_rq(request, fd); fclose(fpin); } } /* ------------------------------------------------------ * read_til_crnl(FILE *) skip over all request info until a CRNL is seen ------------------------------------------------------ */ void read_til_crnl(FILE *fp) { char buf[BUFSIZ]; while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 ) ; } /* ------------------------------------------------------ * process_rq( char *rq, int fd ) do what the request asks for and write reply to fd handles request in a new process rq is HTTP command: GET /foo/bar.html HTTP/1.0 ------------------------------------------------------ */ void process_rq(char *rq, int fd) { void cannot_do(int fd); int not_exist(char *f); void do_404(char *item, int fd); int isadir(char *f); void do_ls(char *dir, int fd); int ends_in_cgi(char *f); void do_exec( char *prog, int fd ); void do_cat(char *f, int fd); char cmd[BUFSIZ], arg[BUFSIZ]; /* create a new process and return if not the child */ if ( fork() != 0 ) return; strcpy(arg, "./"); /* precede args with ./ */ if ( sscanf(rq, "%s%s", cmd, arg+2) != 2 ) return; if ( strcmp(cmd,"GET") != 0 ) cannot_do(fd); else if ( not_exist( arg ) ) do_404(arg, fd ); else if ( isadir( arg ) ) do_ls( arg, fd ); else if ( ends_in_cgi( arg ) ) do_exec( arg, fd ); else do_cat( arg, fd ); } /* ------------------------------------------------------ * the reply header thing: all functions need one if content_type is NULL then don't send content type ------------------------------------------------------ */ void header(FILE *fp, char *content_type) { fprintf(fp, "HTTP/1.0 200 OK\r\n"); if ( content_type ) fprintf(fp, "Content-type: %s\r\n", content_type ); } /* ------------------------------------------------------ * simple functions first: cannot_do(fd) unimplemented HTTP command and do_404(item,fd) no such object ------------------------------------------------------ */ void cannot_do(int fd) { FILE *fp = fdopen(fd,"w"); fprintf(fp, "HTTP/1.0 501 Not Implemented\r\n"); fprintf(fp, "Content-type: text/plain\r\n"); fprintf(fp, "\r\n"); fprintf(fp, "That command is not yet implemented\r\n"); fclose(fp); } void do_404(char *item, int fd) { FILE *fp = fdopen(fd,"w"); fprintf(fp, "HTTP/1.0 404 Not Found\r\n"); fprintf(fp, "Content-type: text/plain\r\n"); fprintf(fp, "\r\n"); fprintf(fp, "The item you requested: %s\r\nis not found\r\n", item); fclose(fp); } /* ------------------------------------------------------ * the directory listing section isadir() uses stat, not_exist() uses stat do_ls runs ls. It should not ------------------------------------------------------ */ int isadir(char *f) { struct stat info; return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) ); } int not_exist(char *f) { struct stat info; return( stat(f,&info) == -1 ); } void do_ls(char *dir, int fd) { FILE *fp ; fp = fdopen(fd,"w"); header(fp, "text/plain"); fprintf(fp,"\r\n"); fflush(fp); dup2(fd,1); dup2(fd,2); close(fd); execlp("ls","ls","-l",dir,NULL); perror(dir); exit(1); } /* ------------------------------------------------------ * the cgi stuff. function to check extension and one to run the program. ------------------------------------------------------ */ /* returns 'extension' of file */ char *file_type(char *f) { char *cp; if ( (cp = strrchr(f, '.' )) != NULL ) return cp+1; return ""; } int ends_in_cgi(char *f) { return ( strcmp( file_type(f), "cgi" ) == 0 ); } void do_exec(char *prog, int fd) { FILE *fp ; fp = fdopen(fd,"w"); header(fp, NULL); fflush(fp); dup2(fd, 1); dup2(fd, 2); close(fd); execl(prog,prog,NULL); perror(prog); } /* ------------------------------------------------------ * do_cat(filename,fd) sends back contents after a header ------------------------------------------------------ */ void do_cat(char *f, int fd) { char *extension = file_type(f); // Assume plain text until we see otherwise char *content = "text/plain"; FILE *fpsock, *fpfile; int c; if ( strcmp(extension,"html") == 0 ) content = "text/html"; else if ( strcmp(extension, "gif") == 0 ) content = "image/gif"; else if ( strcmp(extension, "jpg") == 0 ) content = "image/jpeg"; else if ( strcmp(extension, "jpeg") == 0 ) content = "image/jpeg"; fpsock = fdopen(fd, "w"); fpfile = fopen( f , "r"); if ( fpsock != NULL && fpfile != NULL ) { header( fpsock, content ); fprintf(fpsock, "\r\n"); while( (c = getc(fpfile) ) != EOF ) putc(c, fpsock); fclose(fpfile); fclose(fpsock); } exit(0); } void child_waiter(int signum) { fprintf(stderr, "Caught a SIGCHLD\n"); while(waitpid(-1, NULL, WNOHANG) > 0) ; }