simple_httpd.c revision 133836
10SN/A/*- 21410Sihse * Simple_HTTPd v1.1 - a very small, barebones HTTP server 30SN/A * 40SN/A * Copyright (c) 1998-1999 Marc Nicholas <marc@netstor.com> 50SN/A * All rights reserved. 60SN/A * 7180SN/A * Major rewrite by William Lloyd <wlloyd@slap.net> 80SN/A * 9180SN/A * Redistribution and use in source and binary forms, with or without 100SN/A * modification, are permitted provided that the following conditions 110SN/A * are met: 120SN/A * 1. Redistributions of source code must retain the above copyright 130SN/A * notice, this list of conditions and the following disclaimer. 140SN/A * 2. Redistributions in binary form must reproduce the above copyright 150SN/A * notice, this list of conditions and the following disclaimer in the 160SN/A * documentation and/or other materials provided with the distribution. 170SN/A * 180SN/A * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 190SN/A * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 200SN/A * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21180SN/A * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22180SN/A * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23180SN/A * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 240SN/A * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 250SN/A * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 261410Sihse * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 271410Sihse * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 281410Sihse * SUCH DAMAGE. 291410Sihse * 301410Sihse * $FreeBSD: head/release/picobsd/tinyware/simple_httpd/simple_httpd.c 133836 2004-08-16 09:38:34Z dwmalone $ 311410Sihse */ 32910SN/A 33910SN/A#include <sys/stat.h> 341410Sihse#include <sys/time.h> 355SN/A#include <sys/types.h> 36910SN/A#include <sys/socket.h> 371410Sihse#include <sys/wait.h> 38910SN/A#include <netinet/in.h> 39338SN/A#include <arpa/inet.h> 401426Sihse 411410Sihse#include <fcntl.h> 421426Sihse#include <netdb.h> 431410Sihse#include <signal.h> 441410Sihse#include <stdio.h> 451426Sihse#include <stdlib.h> 461426Sihse#include <string.h> 47311SN/A#include <time.h> 481426Sihse#include <unistd.h> 491426Sihse 501426Sihseint http_port = 80; 511410Sihseint daemonize = 1; 521426Sihseint verbose = 0; 531651Sihseint http_sock, con_sock; 541651Sihse 551120SN/Aconst char *fetch_mode = NULL; 561410Sihsechar homedir[100]; 571410Sihsechar logfile[80]; 581410Sihsechar *adate(void); 591426Sihsevoid init_servconnection(void); 601426Sihsevoid http_date(void); 611426Sihsevoid http_output(const char *html); 621426Sihsevoid http_request(void); 631426Sihsevoid log_line(char *req); 641426Sihsevoid wait_connection(void); 651426Sihse 661426Sihsestruct hostent *hst; 671426Sihsestruct sockaddr_in source; 681426Sihse 691426Sihse/* HTTP basics */ 701426Sihsestatic char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r"; 711426Sihse 721426Sihsestatic char http_200[] = "HTTP/1.0 200 OK\r"; 731426Sihse 741426Sihseconst char *default_mime_type = "application/octet-stream"; 751426Sihse 761426Sihseconst char *mime_type[][2] = { 771426Sihse { "txt", "text/plain" }, 781426Sihse { "htm", "text/html" }, 791426Sihse { "html", "text/html" }, 801426Sihse { "gif", "image/gif" }, 811426Sihse { "jpg", "image/jpeg" }, 821426Sihse { "mp3", "audio/mpeg" } 831426Sihse}; 841426Sihse 851426Sihseconst int mime_type_max = sizeof(mime_type) / sizeof(mime_type[0]) - 1; 861426Sihse 871426Sihse/* Two parts, HTTP Header and then HTML */ 881426Sihsestatic const char *http_404[2] = 891426Sihse {"HTTP/1.0 404 Not found\r\n", 901426Sihse"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\ 911426SihseNot found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n" 921426Sihse}; 931426Sihse 941426Sihsestatic const char *http_405[2] = 951426Sihse {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n", 961426Sihse"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\ 971426SihseThis server only supports GET and HEAD requests.\n</BODY></HTML>\r\n" 981426Sihse}; 991426Sihse 1001426Sihse/* 1011426Sihse * Only called on initial invocation 1021426Sihse */ 1031426Sihsevoid 1041426Sihseinit_servconnection(void) 1051426Sihse{ 1061426Sihse struct sockaddr_in server; 1071426Sihse 1081426Sihse /* Create a socket */ 1091426Sihse http_sock = socket(AF_INET, SOCK_STREAM, 0); 1101426Sihse if (http_sock < 0) { 1111426Sihse perror("socket"); 1121426Sihse exit(1); 1131426Sihse } 1141426Sihse server.sin_family = AF_INET; 1151426Sihse server.sin_port = htons(http_port); 1161426Sihse server.sin_addr.s_addr = INADDR_ANY; 1171426Sihse if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) { 1181426Sihse perror("bind socket"); 1191426Sihse exit(1); 1201426Sihse } 1211426Sihse if (verbose) printf("simple_httpd:%d\n",http_port); 1221426Sihse} 1231426Sihse 1241410Sihse/* 1251120SN/A * Wait here until we see an incoming http request 1261426Sihse */ 1271426Sihsevoid 1281426Sihsewait_connection(void) 1291426Sihse{ 1301426Sihse socklen_t lg; 1311426Sihse 1321426Sihse lg = sizeof(struct sockaddr_in); 1331410Sihse 1341426Sihse con_sock = accept(http_sock, (struct sockaddr *) & source, &lg); 1351426Sihse if (con_sock <= 0) { 1361426Sihse perror("accept"); 13727SN/A exit(1); 1381410Sihse } 1391410Sihse} 1401701Sihse 1411701Sihse/* 1421701Sihse * Print timestamp for HTTP HEAD and GET 1431410Sihse */ 1441410Sihsevoid 1451410Sihsehttp_date(void) 1461410Sihse{ 1471410Sihse time_t tl; 1481410Sihse char buff[50]; 1491410Sihse 1501426Sihse tl = time(NULL); 1511426Sihse strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl)); 1521410Sihse write(con_sock, buff, strlen(buff)); 1531410Sihse /* return(buff); */ 1541410Sihse} 1551156SN/A 1561120SN/A/* 1571936Sihse * Send data to the open socket 1581936Sihse */ 1591936Sihsevoid 1601426Sihsehttp_output(const char *html) 1611426Sihse{ 1621426Sihse write(con_sock, html, strlen(html)); 1631426Sihse write(con_sock, "\r\n", 2); 1641410Sihse} 16527SN/A 1661426Sihse 1671426Sihse/* 1681426Sihse * Create and write the log information to file 1691426Sihse * Log file format is one line per entry 1701426Sihse */ 1711426Sihsevoid 1721651Sihselog_line(char *req) 1731651Sihse{ 1741915Sihse char log_buff[256]; 1751651Sihse char msg[1024]; 1761651Sihse char env_host[80], env_addr[80]; 1771651Sihse long addr; 1781651Sihse FILE *log; 1791426Sihse 1801426Sihse strcpy(log_buff,inet_ntoa(source.sin_addr)); 1811426Sihse sprintf(env_addr, "REMOTE_ADDR=%s",log_buff); 1821426Sihse 1831651Sihse addr=inet_addr(log_buff); 1841426Sihse 1851410Sihse strcpy(msg,adate()); 1861410Sihse strcat(msg," "); 1871410Sihse hst=gethostbyaddr((char*) &addr, 4, AF_INET); 1881651Sihse 1891410Sihse /* If DNS hostname exists */ 1901426Sihse if (hst) { 1911651Sihse strcat(msg,hst->h_name); 1921651Sihse sprintf(env_host, "REMOTE_HOST=%s",hst->h_name); 1931651Sihse } 1941651Sihse strcat(msg," ("); 1951651Sihse strcat(msg,log_buff); 1961651Sihse strcat(msg,") "); 1971651Sihse strcat(msg,req); 1981651Sihse 1991651Sihse if (daemonize) { 2001651Sihse log=fopen(logfile,"a"); 2011651Sihse fprintf(log,"%s\n",msg); 2021651Sihse fclose(log); 2031651Sihse } else 2041651Sihse printf("%s\n",msg); 2051410Sihse 2061410Sihse /* This is for CGI scripts */ 2071410Sihse putenv(env_addr); 2081410Sihse putenv(env_host); 2090SN/A} 2101426Sihse 2111410Sihse/* 2121410Sihse * We have a connection. Identify what type of request GET, HEAD, CGI, etc 2131410Sihse * and do what needs to be done 2141410Sihse */ 2151410Sihsevoid 2161410Sihsehttp_request(void) 2171410Sihse{ 2181410Sihse int fd, lg, i; 2191410Sihse int cmd = 0; 2201426Sihse char *p, *par; 2211426Sihse const char *filename, *c, *ext, *type; 2221426Sihse struct stat file_status; 2231426Sihse char req[1024]; 2241426Sihse char buff[8192]; 2251426Sihse 2261426Sihse lg = read(con_sock, req, 1024); 2271426Sihse 2281651Sihse if ((p=strstr(req,"\n"))) *p=0; 2291651Sihse if ((p=strstr(req,"\r"))) *p=0; 2301651Sihse 2311410Sihse log_line(req); 2321862Serikj 2331862Serikj c = strtok(req, " "); 2341410Sihse 2351410Sihse /* Error msg if request is nothing */ 2361410Sihse if (c == NULL) { 2371410Sihse http_output(http_404[0]); 2381410Sihse http_output(http_404[1]); 2391410Sihse goto end_request; 2401426Sihse } 2411426Sihse 2421426Sihse if (strncmp(c, "GET", 3) == 0) cmd = 1; 2431426Sihse if (strncmp(c, "HEAD", 4) == 0) cmd = 2; 2441426Sihse 2451426Sihse /* Do error msg for any other type of request */ 2461426Sihse if (cmd == 0) { 2471426Sihse http_output(http_405[0]); 2481426Sihse http_output(http_405[1]); 2491426Sihse goto end_request; 2501426Sihse } 2511426Sihse 2521426Sihse filename = strtok(NULL, " "); 2531327SN/A 2541628Sihse c = strtok(NULL, " "); 2551628Sihse if (fetch_mode != NULL) filename=fetch_mode; 2561628Sihse if (filename == NULL || 2571410Sihse strlen(filename)==1) filename="/index.html"; 2581410Sihse 2591410Sihse while (filename[0]== '/') filename++; 2601410Sihse 2611410Sihse /* CGI handling. Untested */ 2621410Sihse if (!strncmp(filename,"cgi-bin/",8)) 2631410Sihse { 2641410Sihse par=0; 2651410Sihse if ((par=strstr(filename,"?"))) 2661426Sihse { 2671426Sihse *par=0; 2681426Sihse par++; 2691410Sihse } 2701651Sihse if (access(filename,X_OK)) goto conti; 2711426Sihse stat (filename,&file_status); 2721426Sihse if (setuid(file_status.st_uid)) return; 2731410Sihse if (seteuid(file_status.st_uid)) return; 2741410Sihse if (!fork()) 2751410Sihse { 2761410Sihse close(1); 2771410Sihse dup(con_sock); 2780SN/A /*printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n");*/ 2791426Sihse printf("HTTP/1.0 200 OK\r\n"); 2801426Sihse /* Plug in environment variable, others in log_line */ 2811426Sihse putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD"); 2821623Sihse 2831862Serikj execlp (filename,filename,par,(char *)0); 2841426Sihse } 2851426Sihse wait(&i); 2861862Serikj return; 2871426Sihse } 2881426Sihse conti: 2891426Sihse if (filename == NULL) { 2901426Sihse http_output(http_405[0]); 2911426Sihse http_output(http_405[1]); 2921426Sihse goto end_request; 2931426Sihse } 2941426Sihse /* End of CGI handling */ 2951862Serikj 2961623Sihse /* Reject any request with '..' in it, bad hacker */ 2971862Serikj c = filename; 2981862Serikj while (*c != '\0') 2991862Serikj if (c[0] == '.' && c[1] == '.') { 3001862Serikj http_output(http_404[0]); 3011623Sihse http_output(http_404[1]); 3021623Sihse goto end_request; 3031623Sihse } else 3041426Sihse c++; 3051426Sihse 3061426Sihse /* Open filename */ 3071426Sihse fd = open(filename, O_RDONLY); 3081862Serikj if (fd < 0) { 3091410Sihse http_output(http_404[0]); 3101410Sihse http_output(http_404[1]); 3111623Sihse goto end_request; 3121745Sihse } 3131745Sihse 3141745Sihse /* Get file status information */ 3151745Sihse if (fstat(fd, &file_status) < 0) { 3161745Sihse http_output(http_404[0]); 3171623Sihse http_output(http_404[1]); 3181623Sihse goto end_request2; 3191651Sihse } 3201651Sihse 3211900Sihse /* Is it a regular file? */ 3221745Sihse if (!S_ISREG(file_status.st_mode)) { 3231651Sihse http_output(http_404[0]); 3241651Sihse http_output(http_404[1]); 3251926Serikj goto end_request2; 3261745Sihse } 3271745Sihse 3281651Sihse /* Past this point we are serving either a GET or HEAD */ 3291623Sihse /* Print all the header info */ 3301410Sihse http_output(http_200); 331 http_output(httpd_server_ident); 332 http_date(); 333 334 sprintf(buff, "Content-length: %lld\r\n", file_status.st_size); 335 write(con_sock, buff, strlen(buff)); 336 337 strcpy(buff, "Content-type: "); 338 type = default_mime_type; 339 if ((ext = strrchr(filename, '.')) != NULL) { 340 for (i = mime_type_max; i >= 0; i--) 341 if (strcmp(ext + 1, mime_type[i][0]) == 0) { 342 type = mime_type[i][1]; 343 break; 344 } 345 } 346 strcat(buff, type); 347 http_output(buff); 348 349 strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime)); 350 write(con_sock, buff, strlen(buff)); 351 352 /* Send data only if GET request */ 353 if (cmd == 1) { 354 while ((lg = read(fd, buff, 8192)) > 0) 355 write(con_sock, buff, lg); 356 } 357 358end_request2: 359 close(fd); 360end_request: 361 close(con_sock); 362 363} 364 365/* 366 * Simple httpd server for use in PicoBSD or other embedded application. 367 * Should satisfy simple httpd needs. For more demanding situations 368 * apache is probably a better (but much larger) choice. 369 */ 370int 371main(int argc, char *argv[]) 372{ 373 int ch, ld; 374 int httpd_group = 65534; 375 pid_t server_pid; 376 377 /* Default for html directory */ 378 strcpy (homedir,getenv("HOME")); 379 if (!geteuid()) strcpy (homedir,"/httphome"); 380 else strcat (homedir,"/httphome"); 381 382 /* Defaults for log file */ 383 if (geteuid()) { 384 strcpy(logfile,getenv("HOME")); 385 strcat(logfile,"/"); 386 strcat(logfile,"jhttp.log"); 387 } else 388 strcpy(logfile,"/var/log/jhttpd.log"); 389 390 /* Parse command line arguments */ 391 while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1) 392 switch (ch) { 393 case 'd': 394 strcpy(homedir,optarg); 395 break; 396 case 'f': 397 daemonize = 0; 398 verbose = 1; 399 fetch_mode = optarg; 400 break; 401 case 'g': 402 httpd_group = atoi(optarg); 403 break; 404 case 'l': 405 strcpy(logfile,optarg); 406 break; 407 case 'p': 408 http_port = atoi(optarg); 409 break; 410 case 'v': 411 verbose = 1; 412 break; 413 case 'D': 414 daemonize = 0; 415 break; 416 case '?': 417 case 'h': 418 default: 419 printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n"); 420 exit(1); 421 /* NOTREACHED */ 422 } 423 424 /* Not running as root and no port supplied, assume 1080 */ 425 if ((http_port == 80) && geteuid()) { 426 http_port = 1080; 427 } 428 429 /* Do we really have rights in the html directory? */ 430 if (fetch_mode == NULL) { 431 if (chdir(homedir)) { 432 perror("chdir"); 433 puts(homedir); 434 exit(1); 435 } 436 } 437 438 /* Create log file if it doesn't exit */ 439 if ((access(logfile,W_OK)) && daemonize) { 440 ld = open (logfile,O_WRONLY); 441 chmod (logfile,00600); 442 close(ld); 443 } 444 445 init_servconnection(); 446 447 if (verbose) { 448 printf("Server started with options \n"); 449 printf("port: %d\n",http_port); 450 if (fetch_mode == NULL) printf("html home: %s\n",homedir); 451 if (daemonize) printf("logfile: %s\n",logfile); 452 } 453 454 /* httpd is spawned */ 455 if (daemonize) { 456 if ((server_pid = fork()) != 0) { 457 wait3(0,WNOHANG,0); 458 if (verbose) printf("pid: %d\n",server_pid); 459 exit(0); 460 } 461 wait3(0,WNOHANG,0); 462 } 463 464 if (fetch_mode == NULL) setpgrp(0,httpd_group); 465 466 /* How many connections do you want? 467 * Keep this lower than the available number of processes 468 */ 469 if (listen(http_sock,15) < 0) exit(1); 470 471 label: 472 wait_connection(); 473 474 if (fork()) { 475 wait3(0,WNOHANG,0); 476 close(con_sock); 477 goto label; 478 } 479 480 http_request(); 481 482 wait3(0,WNOHANG,0); 483 exit(0); 484} 485 486 487char * 488adate(void) 489{ 490 static char out[50]; 491 time_t now; 492 struct tm *t; 493 time(&now); 494 t = localtime(&now); 495 sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d", 496 t->tm_hour, t->tm_min, t->tm_sec, 497 t->tm_mday, t->tm_mon+1, t->tm_year ); 498 return out; 499} 500