/* Pews - Personal Web Server -------------------------- Written by d1s4st3r http://xoomer.alice.it/mental_insomnia/ http://d1s4st3r.blogspot.com/ http://d1s4st3r.deviantart.com/ To compile on GNU/Linux: gcc -o pews pews.c To compile on Sun Solaris: gcc -o pews pews.c -lsocket -lnsl -lresolv */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PROGRAMNAME "Pews" #define PROGRAMVERS "0.1-alpha1" #define MAXMSG 1024 #define MAXPATH 1024 #define ERRORHEAD "\nPews v0.1 - Error\n

Error!

\n

\n" #define ERRORTAIL "\n

\n
\nPews v0.1" #define INDEX "/index.html" static char *progname; /* name of the running executable file */ static char *BASEADDR; /* server's root directory (the one which contains index.html) */ static int SERVER_PORT; /* local port the server should listen on */ static int MAXLISTEN; /* maximum number of allowed connections at the same time */ static int VERBOSITY; /* verbosity (0=off, 1=on) */ typedef struct { int s, len, curr; char msg[MAXMSG]; } conn; struct globalArgs_t { const char *rootdir; /* -r option: server's root directory (the one which contains index.html) */ int port; /* -p option: local port the server should listen on */ int maxlisten; /* -m option: maximum number of allowed connections at the same time */ int verbosity; /* -v option: verbosity (0=off, 1=on) */ } globalArgs; static const char *optString = "r:p:m:vh?V"; /* command line option flags */ /* this function shows the program usage */ static void display_usage() { printf("Usage: %s []\n", progname); printf("where are:\n"); printf(" -r the root directory which contains index.html\n"); printf(" -p the port the server should listen on\n"); printf(" -m max number of allowed connections\n"); printf(" -h|-? this help\n"); printf(" -v verbose mode\n"); printf(" -V program version\n"); fflush(stdout); exit(EXIT_SUCCESS); } /* this function shows the program version */ static void display_version() { printf("%s v%s\n", PROGRAMNAME, PROGRAMVERS); exit(EXIT_SUCCESS); } /* returns next character in stream, skipping '\r' */ static int next(conn *c) { char retval; do { if (c->len==c->curr) { c->len = read(c->s, c->msg, MAXMSG); if (c->len<0) { perror("read"); exit(EXIT_FAILURE); } if (c->len == 0) return EOF; c->curr = 0; } retval = c->msg[c->curr++]; } while (retval == '\r'); return retval; } /* returns standard error messages */ static void error(int s, int num) { static char *errors[] = {"Your browser sent a query that this server does not understand.", "The URL you requested was not found on this server.", "The URL you requested was too long! Sorry."}; if ((num<0)||(num>sizeof(errors))) { fprintf(stderr, "Server: illegal error message requested: %d\n", num); exit(EXIT_FAILURE); } if (write(s, ERRORHEAD, strlen(ERRORHEAD))<0) { perror("server: errorhead write failed\n"); exit(EXIT_FAILURE); } if (write(s, errors[num], strlen(errors[num]))<0) { perror("server: errormsg write failed\n"); exit(EXIT_FAILURE); } if (write(s, ERRORTAIL, strlen(ERRORTAIL))<0) { perror("server: errortail write failed\n"); exit(EXIT_FAILURE); } } /* actual server code */ static void server(int s) { conn c; int i = 0; int ncount = 0; int infile; struct stat stbuf; char path[MAXPATH+11], ch, *p, buff[MAXMSG]; /* +11 here is to cover "/index.html" */ c.s = s; c.len = 0; c.curr = 0; /* verify that the "GET " portion of the request is okay */ if (!((next(&c)=='G')&&(next(&c)=='E')&&(next(&c)=='T')&&(next(&c)==' '))) { error(c.s, 0); return; } /* start the path with the base address */ p = BASEADDR; while (*p!=0) path[i++] = *(p++); /* collect the requested path up to the space (if the path is terminated by a \n, don't bother getting the other newline) */ while (1) { ch = next(&c); if (ch==' ') break; if (ch=='\n') { ncount = 2; break; } path[i++] = ch; if (i==MAXPATH) /* oh no, the path is too long! */ { error(c.s, 2); return; } } path[i] = 0; /* wait for two \n's to go by */ while (ncount<2) { if (next(&c)=='\n') ncount++; else ncount = 0; } /* try to open the generated path */ infile = open(path, O_RDONLY); if (infile<0) { if ((errno==EACCES)||(errno==ENAMETOOLONG)||(errno==ENOENT)||(errno==ENOTDIR)||(errno==ELOOP)) { /* URL not found... */ error(c.s, 1); return; } perror("server: path open failed badly\n"); exit(EXIT_FAILURE); } if (fstat(infile, &stbuf)<0) { perror("server: fstat failed\n"); exit(EXIT_FAILURE); } /* if it's not a regular file or directory, it's no good */ if (!(S_ISREG(stbuf.st_mode)||S_ISDIR(stbuf.st_mode))) { error(c.s, 1); return; } /* directory kludge; if it's a directory, try adding /index.html */ if (S_ISDIR(stbuf.st_mode)) { p = INDEX; while (*p!=0) path[i++] = *(p++); path[i] = 0; close(infile); infile = open(path, O_RDONLY); if (infile<0) { if ((errno==EACCES)||(errno==ENAMETOOLONG)||(errno==ENOENT)||(errno==ENOTDIR)||(errno==ELOOP)) { /* URL not found... */ error(c.s, 1); return; } perror("server: path open index.html failed badly\n"); exit(EXIT_FAILURE); } } /* now send the opened file over the wire */ do { i = read(infile, buff, MAXMSG); if (i>0) if (write(c.s, buff, i)<0) { perror("server: sending file failed\n"); exit(EXIT_FAILURE); } } while (i>0); if (i<0) { perror("server: reading file failed\n"); exit(EXIT_FAILURE); } } /* main */ int main(int argc, char *argv[]) { int s; /* socket */ struct sockaddr_in from; /* structure for the remote clients */ static struct sockaddr_in me_in; /* structure for the local machine */ int fromlen; int news; int pid; int opt; progname = argv[0]; /* initialize globalArgs with default values */ /*globalArgs.rootdir = "/";*/ if ((globalArgs.rootdir=getcwd(NULL, 64))==NULL){perror("pwd");exit(2);} /* sets the root dir to the current directory */ globalArgs.port = 8080; globalArgs.maxlisten = 10; globalArgs.verbosity = 0; opt = getopt(argc, argv, optString); /* the following while loop examines the command line arguments */ while (opt!=(-1)) { switch(opt) { case 'r': globalArgs.rootdir = optarg; break; case 'p': globalArgs.port = atoi(optarg); break; case 'm': globalArgs.maxlisten = atoi(optarg); break; case 'v': globalArgs.verbosity++; break; case 'h': case '?': display_usage(); break; case 'V': display_version(); break; default: /* you will never get here... */ break; } opt = getopt(argc, argv, optString); } BASEADDR = globalArgs.rootdir; /* global variable for root dir (needed by child processes) */ SERVER_PORT = globalArgs.port; MAXLISTEN = globalArgs.maxlisten; VERBOSITY = globalArgs.verbosity; if (VERBOSITY) printf("Local root directory set to: %s\n", BASEADDR); /* this creates the socket */ if ((s=socket(PF_INET, SOCK_STREAM, 0))<0) /* TCP connection */ { perror("socket"); exit(EXIT_FAILURE); } /* this binds the socket address to a connection */ me_in.sin_family = AF_INET; me_in.sin_addr.s_addr = htonl(INADDR_ANY); /* anyone can connect */ me_in.sin_port = htons(SERVER_PORT); if (bind(s, (struct sockaddr *)&me_in, sizeof(me_in))<0) { perror("bind"); exit(EXIT_FAILURE); } if (VERBOSITY) printf("Listening on port %d...\n", SERVER_PORT); /* this makes the server accept MAXLISTEN connection requests at the same time */ if (listen(s, MAXLISTEN)<0) { perror("listen"); exit(EXIT_FAILURE); } if (VERBOSITY) printf("Accepting %d connections.\n", MAXLISTEN); /* loop forever accepting connections from anyone */ for (;;) { /* waiting for someone to connect; accept() returns a new connection for the established connection */ fromlen = sizeof(from); if ((news=accept(s, (struct sockaddr *)&from, &fromlen))<0) { perror("accept"); exit(EXIT_FAILURE); } /* some client connected; fork off a process to deal with the client (the main program can continue waiting for new connections) */ if ((pid=fork())<0) { perror("fork"); exit(EXIT_FAILURE); } else if (pid==0) /* child process that will handle the connection */ { struct hostent *hp = gethostbyaddr((char *)&from.sin_addr.s_addr, sizeof(from.sin_addr.s_addr), AF_INET); char *remote_ip = (char *)inet_ntoa(from.sin_addr); char *remote_host = hp->h_name; int remote_port = ntohs(from.sin_port); if (VERBOSITY) printf("New connection from: %s (%s:%d)\n", remote_host, remote_ip, remote_port); close(s); /* child has no need for the parent's socket */ server(news); /* serve the established connection */ exit(EXIT_SUCCESS); } close(news); } }