
#include "uhttpd.h"

void log(int type, char *s1, char *s2, int num)
{

    int fd;
    char logbuffer[BUFSIZE * 2];

    switch (type) {
    case ERROR:
	(void) sprintf(logbuffer, "ERROR: %s:%s Errno=%d exiting pid=%d",
		       s1, s2, errno, getpid());
	break;
    case SORRY:
	(void) sprintf(logbuffer,
		       "<HTML><BODY><H1>uhttpd Web Server Sorry: %s %s</H1></BODY></HTML>\r\n",
		       s1, s2);
	(void) write(num, logbuffer, strlen(logbuffer));
	(void) sprintf(logbuffer, "SORRY: %s:%s", s1, s2);
	break;
    case LOG:
	(void) sprintf(logbuffer, " INFO: %s:%s:%d", s1, s2, num);
	break;
    }

    /* no checks here, nothing can be done a failure anyway */
    if ((fd =
	 open("uhttpd.log", O_CREAT | O_WRONLY | O_APPEND, 0644)) >= 0) {
	(void) write(fd, logbuffer, strlen(logbuffer));
	(void) write(fd, "\n", 1);
	(void) close(fd);
    }
    if (type == ERROR || type == SORRY)
	exit(3);
}

/* this is a child web server process, so we can exit on errors */
void web(int fd, int hit)
{

    int j, file_fd, buflen, len;
    long i, ret;
    char *fstr;
    static char buffer[BUFSIZE + 1];	/* static so zero filled */

    ret = read(fd, buffer, BUFSIZE);	/* read Web request in one go */

    if (ret == 0 || ret == -1) {	/* read failure stop now */
	log(SORRY, "failed to read browser request", "", fd);
    }

    if (ret > 0 && ret < BUFSIZE)	/* return code is valid chars */
	buffer[ret] = 0;	/* terminate the buffer */
    else
	buffer[0] = 0;

    for (i = 0; i < ret; i++)	/* remove CF and LF characters */
	if (buffer[i] == '\r' || buffer[i] == '\n')
	    buffer[i] = '*';

    log(LOG, "request", buffer, hit);

    if (strncmp(buffer, "GET ", 4) && strncmp(buffer, "get ", 4))
	log(SORRY, "Only simple GET operation supported", buffer, fd);

    for (i = 4; i < BUFSIZE; i++) {	/* null terminate after the second space 
					   to ignore extra stuff */
	if (buffer[i] == ' ') {	/* string is "GET URL " +lots of 
				   other stuff */
	    buffer[i] = 0;
	    break;
	}
    }

    for (j = 0; j < i - 1; j++)	/* check for illegal parent directory use 
				   .. */
	if (buffer[j] == '.' && buffer[j + 1] == '.')
	    log(SORRY, "Parent directory (..) path names not supported",
		buffer, fd);

    if (!strncmp(&buffer[0], "GET /\0", 6) || !strncmp(&buffer[0], "get /\0", 6))	/* convert no filename to 
											   index file */
	(void) strcpy(buffer, "GET /index.html");

    /* work out the file type and check we support it */
    buflen = strlen(buffer);
    fstr = (char *) 0;
    for (i = 0; extensions[i].ext != 0; i++) {
	len = strlen(extensions[i].ext);
	if (!strncmp(&buffer[buflen - len], extensions[i].ext, len)) {
	    fstr = extensions[i].filetype;
	    break;
	}
    }

    if (fstr == 0)
	log(SORRY, "file extension type not supported", buffer, fd);

    if ((file_fd = open(&buffer[5], O_RDONLY)) == -1)	/* open the file 
							   for reading */
	log(SORRY, "failed to open file", &buffer[5], fd);

    log(LOG, "SEND", &buffer[5], hit);

    (void) sprintf(buffer, "HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n",
		   fstr);

    (void) write(fd, buffer, strlen(buffer));

    /* send file in 8KB block - last block may be smaller */
    while ((ret = read(file_fd, buffer, BUFSIZE)) > 0) {
	(void) write(fd, buffer, ret);
    }
#ifdef LINUX
    sleep(1);			/* to allow socket to drain */
#endif
    exit(1);
}

main(int argc, char **argv)
{

    int i, port, pid, listenfd, socketfd, hit;
    size_t length;
    char *str;
    static struct sockaddr_in cli_addr;	/* static = initialised to 
					   zeros */
    static struct sockaddr_in serv_addr;	/* static = initialised to 
						   zeros */

    if (argc < 3 || argc > 3 || !strcmp(argv[1], "-?")) {
	(void) printf("hint: uhttpd Port-Number Top-Directory\n\n"
		      "\tuhttpd is a small and very safe mini web server\n"
		      "\tuhttpd only servers out file/web pages with extensions named  below\n"
		      "\t and only from the named directory or its sub-directories.\n"
		      "\tThere is no fancy features = safe and secure.\n\n"
		      "\tExample: uhttpd 8181 /home/nwebdir &\n\n"
		      "\tOnly Supports:");
	for (i = 0; extensions[i].ext != 0; i++)
	    (void) printf(" %s", extensions[i].ext);

	(void)
	    printf
	    ("\n\tNot Supported: URLs including \"..\",  Java, Javascript, CGI\n"
	     "\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
	     "\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n");
	exit(0);
    }

    if (!strncmp(argv[2], "/", 2) || !strncmp(argv[2], "/etc", 5) ||
	!strncmp(argv[2], "/bin", 5) || !strncmp(argv[2], "/lib", 5) ||
	!strncmp(argv[2], "/tmp", 5) || !strncmp(argv[2], "/usr", 5) ||
	!strncmp(argv[2], "/dev", 5) || !strncmp(argv[2], "/sbin", 6)) {

	(void) printf("ERROR: Bad top directory %s, see uhttpd -?\n",
		      argv[2]);
	exit(3);
    }

    if (chdir(argv[2]) == -1) {
	(void) printf("ERROR: Can't Change to directory %s\n", argv[2]);
	exit(4);
    }



    /* Become deamon + unstopable and no zombies children 
       (= no wait()) */
    if (fork() != 0)
	return 0;		/* parent returns OK to shell */
/* XXX-jrf SIGCLD ??? */
/*        (void)signal(SIGCLD, SIG_IGN); /* ignore child death */
    (void) signal(SIGHUP, SIG_IGN);	/* ignore terminal hangups */

    for (i = 0; i < 32; i++)
	(void) close(i);	/* close open files */

    (void) setpgrp();		/* break away from process group */
    log(LOG, "uhttpd starting", argv[1], getpid());
    /* setup the network socket */
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	log(ERROR, "system call", "socket", 0);

    port = atoi(argv[1]);

    if (port < 0 || port > 60000)
	log(ERROR, "Invalid port number (try 1->60000)", argv[1], 0);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(port);

    if (bind(listenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
	< 0)
	log(ERROR, "system call", "bind", 0);

    if (listen(listenfd, 64) < 0)
	log(ERROR, "system call", "listen", 0);

    for (hit = 1;; hit++) {
	length = sizeof(cli_addr);
	if ((socketfd = accept(listenfd, (struct sockaddr *)
			       &cli_addr, &length)) < 0)
	    log(ERROR, "system call", "accept", 0);

	if ((pid = fork()) < 0) {
	    log(ERROR, "system call", "fork", 0);
	} else {
	    if (pid == 0) {	/* child */
		(void) close(listenfd);
		web(socketfd, hit);	/* never returns */

	    } else {		/* parent */
		(void) close(socketfd);
	    }
	}
    }
}

