simple_httpd.c revision 113129
1/*- 2 * Simple_HTTPd v1.1 - a very small, barebones HTTP server 3 * 4 * Copyright (c) 1998-1999 Marc Nicholas <marc@netstor.com> 5 * All rights reserved. 6 * 7 * Major rewrite by William Lloyd <wlloyd@slap.net> 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 * $FreeBSD: head/release/picobsd/tinyware/simple_httpd/simple_httpd.c 113129 2003-04-05 17:15:38Z dwmalone $ 31 */ 32 33#include <sys/stat.h> 34#include <sys/time.h> 35#include <sys/types.h> 36#include <sys/socket.h> 37#include <sys/wait.h> 38#include <netinet/in.h> 39#include <arpa/inet.h> 40 41#include <fcntl.h> 42#include <netdb.h> 43#include <signal.h> 44#include <stdio.h> 45#include <stdlib.h> 46#include <string.h> 47#include <time.h> 48#include <unistd.h> 49 50int http_port = 80; 51int daemonize = 1; 52int verbose = 0; 53int http_sock, con_sock; 54 55const char *fetch_mode = NULL; 56char homedir[100]; 57char logfile[80]; 58char *adate(void); 59void init_servconnection(void); 60void http_date(void); 61void http_output(const char *html); 62void http_request(void); 63void log_line(char *req); 64void wait_connection(void); 65 66struct hostent *hst; 67struct sockaddr_in source; 68 69/* HTTP basics */ 70static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r"; 71 72static char http_200[] = "HTTP/1.0 200 OK\r"; 73 74/* Two parts, HTTP Header and then HTML */ 75static const char *http_404[2] = 76 {"HTTP/1.0 404 Not found\r\n", 77"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\ 78Not found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n" 79}; 80 81static const char *http_405[2] = 82 {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n", 83"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\ 84This server only supports GET and HEAD requests.\n</BODY></HTML>\r\n" 85}; 86 87/* 88 * Only called on initial invocation 89 */ 90void 91init_servconnection(void) 92{ 93 struct sockaddr_in server; 94 95 /* Create a socket */ 96 http_sock = socket(AF_INET, SOCK_STREAM, 0); 97 if (http_sock < 0) { 98 perror("socket"); 99 exit(1); 100 } 101 server.sin_family = AF_INET; 102 server.sin_port = htons(http_port); 103 server.sin_addr.s_addr = INADDR_ANY; 104 if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) { 105 perror("bind socket"); 106 exit(1); 107 } 108 if (verbose) printf("simple_httpd:%d\n",http_port); 109} 110 111/* 112 * Wait here until we see an incoming http request 113 */ 114void 115wait_connection(void) 116{ 117 socklen_t lg; 118 119 lg = sizeof(struct sockaddr_in); 120 121 con_sock = accept(http_sock, (struct sockaddr *) & source, &lg); 122 if (con_sock <= 0) { 123 perror("accept"); 124 exit(1); 125 } 126} 127 128/* 129 * Print timestamp for HTTP HEAD and GET 130 */ 131void 132http_date(void) 133{ 134 time_t tl; 135 char buff[50]; 136 137 tl = time(NULL); 138 strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl)); 139 write(con_sock, buff, strlen(buff)); 140 /* return(buff); */ 141} 142 143/* 144 * Send data to the open socket 145 */ 146void 147http_output(const char *html) 148{ 149 write(con_sock, html, strlen(html)); 150 write(con_sock, "\r\n", 2); 151} 152 153 154/* 155 * Create and write the log information to file 156 * Log file format is one line per entry 157 */ 158void 159log_line(char *req) 160{ 161 char log_buff[256]; 162 char msg[1024]; 163 char env_host[80], env_addr[80]; 164 long addr; 165 FILE *log; 166 167 strcpy(log_buff,inet_ntoa(source.sin_addr)); 168 sprintf(env_addr, "REMOTE_ADDR=%s",log_buff); 169 170 addr=inet_addr(log_buff); 171 172 strcpy(msg,adate()); 173 strcat(msg," "); 174 hst=gethostbyaddr((char*) &addr, 4, AF_INET); 175 176 /* If DNS hostname exists */ 177 if (hst) { 178 strcat(msg,hst->h_name); 179 sprintf(env_host, "REMOTE_HOST=%s",hst->h_name); 180 } 181 strcat(msg," ("); 182 strcat(msg,log_buff); 183 strcat(msg,") "); 184 strcat(msg,req); 185 186 if (daemonize) { 187 log=fopen(logfile,"a"); 188 fprintf(log,"%s\n",msg); 189 fclose(log); 190 } else 191 printf("%s\n",msg); 192 193 /* This is for CGI scripts */ 194 putenv(env_addr); 195 putenv(env_host); 196} 197 198/* 199 * We have a connection. Identify what type of request GET, HEAD, CGI, etc 200 * and do what needs to be done 201 */ 202void 203http_request(void) 204{ 205 int fd, lg, i; 206 int cmd = 0; 207 char *p, *par; 208 const char *filename, *c; 209 struct stat file_status; 210 char req[1024]; 211 char buff[8192]; 212 213 lg = read(con_sock, req, 1024); 214 215 if ((p=strstr(req,"\n"))) *p=0; 216 if ((p=strstr(req,"\r"))) *p=0; 217 218 log_line(req); 219 220 c = strtok(req, " "); 221 222 /* Error msg if request is nothing */ 223 if (c == NULL) { 224 http_output(http_404[0]); 225 http_output(http_404[1]); 226 goto end_request; 227 } 228 229 if (strncmp(c, "GET", 3) == 0) cmd = 1; 230 if (strncmp(c, "HEAD", 4) == 0) cmd = 2; 231 232 /* Do error msg for any other type of request */ 233 if (cmd == 0) { 234 http_output(http_405[0]); 235 http_output(http_405[1]); 236 goto end_request; 237 } 238 239 filename = strtok(NULL, " "); 240 241 c = strtok(NULL, " "); 242 if (fetch_mode != NULL) filename=fetch_mode; 243 if (filename == NULL || 244 strlen(filename)==1) filename="/index.html"; 245 246 while (filename[0]== '/') filename++; 247 248 /* CGI handling. Untested */ 249 if (!strncmp(filename,"cgi-bin/",8)) 250 { 251 par=0; 252 if ((par=strstr(filename,"?"))) 253 { 254 *par=0; 255 par++; 256 } 257 if (access(filename,X_OK)) goto conti; 258 stat (filename,&file_status); 259 if (setuid(file_status.st_uid)) return; 260 if (seteuid(file_status.st_uid)) return; 261 if (!fork()) 262 { 263 close(1); 264 dup(con_sock); 265 /*printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n");*/ 266 printf("HTTP/1.0 200 OK\r\n"); 267 /* Plug in environment variable, others in log_line */ 268 putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD"); 269 270 execlp (filename,filename,par,(char *)0); 271 } 272 wait(&i); 273 return; 274 } 275 conti: 276 if (filename == NULL) { 277 http_output(http_405[0]); 278 http_output(http_405[1]); 279 goto end_request; 280 } 281 /* End of CGI handling */ 282 283 /* Reject any request with '..' in it, bad hacker */ 284 c = filename; 285 while (*c != '\0') 286 if (c[0] == '.' && c[1] == '.') { 287 http_output(http_404[0]); 288 http_output(http_404[1]); 289 goto end_request; 290 } else 291 c++; 292 293 /* Open filename */ 294 fd = open(filename, O_RDONLY); 295 if (fd < 0) { 296 http_output(http_404[0]); 297 http_output(http_404[1]); 298 goto end_request; 299 } 300 301 /* Get file status information */ 302 if (fstat(fd, &file_status) < 0) { 303 http_output(http_404[0]); 304 http_output(http_404[1]); 305 goto end_request2; 306 } 307 308 /* Is it a regular file? */ 309 if (!S_ISREG(file_status.st_mode)) { 310 http_output(http_404[0]); 311 http_output(http_404[1]); 312 goto end_request2; 313 } 314 315 /* Past this point we are serving either a GET or HEAD */ 316 /* Print all the header info */ 317 http_output(http_200); 318 http_output(httpd_server_ident); 319 http_date(); 320 321 sprintf(buff, "Content-length: %lld\r\n", file_status.st_size); 322 write(con_sock, buff, strlen(buff)); 323 324 if (strstr(filename,".txt")) { 325 strcpy(buff,"Content-type: text/plain\r\n"); 326 } else if (strstr(filename,".html") || strstr(filename,".htm")) { 327 strcpy(buff,"Content-type: text/html\r\n"); 328 } else if (strstr(filename,".gif")) { 329 strcpy(buff,"Content-type: image/gif\r\n"); 330 } else if (strstr(filename,".jpg")) { 331 strcpy(buff,"Content-type: image/jpeg\r\n"); 332 } else { 333 /* Take a guess at content if we don't have something already */ 334 strcpy(buff,"Content-type: "); 335 strcat(buff,strstr(filename,".")+1); 336 strcat(buff,"\r\n"); 337 } 338 write(con_sock, buff, strlen(buff)); 339 340 strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime)); 341 write(con_sock, buff, strlen(buff)); 342 343 /* Send data only if GET request */ 344 if (cmd == 1) { 345 while ((lg = read(fd, buff, 8192)) > 0) 346 write(con_sock, buff, lg); 347 } 348 349end_request2: 350 close(fd); 351end_request: 352 close(con_sock); 353 354} 355 356/* 357 * Simple httpd server for use in PicoBSD or other embedded application. 358 * Should satisfy simple httpd needs. For more demanding situations 359 * apache is probably a better (but much larger) choice. 360 */ 361int 362main(int argc, char *argv[]) 363{ 364 int ch, ld; 365 int httpd_group = 65534; 366 pid_t server_pid; 367 368 /* Default for html directory */ 369 strcpy (homedir,getenv("HOME")); 370 if (!geteuid()) strcpy (homedir,"/httphome"); 371 else strcat (homedir,"/httphome"); 372 373 /* Defaults for log file */ 374 if (geteuid()) { 375 strcpy(logfile,getenv("HOME")); 376 strcat(logfile,"/"); 377 strcat(logfile,"jhttp.log"); 378 } else 379 strcpy(logfile,"/var/log/jhttpd.log"); 380 381 /* Parse command line arguments */ 382 while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1) 383 switch (ch) { 384 case 'd': 385 strcpy(homedir,optarg); 386 break; 387 case 'f': 388 daemonize = 0; 389 verbose = 1; 390 fetch_mode = optarg; 391 break; 392 case 'g': 393 httpd_group = atoi(optarg); 394 break; 395 case 'l': 396 strcpy(logfile,optarg); 397 break; 398 case 'p': 399 http_port = atoi(optarg); 400 break; 401 case 'v': 402 verbose = 1; 403 break; 404 case 'D': 405 daemonize = 0; 406 break; 407 case '?': 408 case 'h': 409 default: 410 printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n"); 411 exit(1); 412 /* NOTREACHED */ 413 } 414 415 /* Not running as root and no port supplied, assume 1080 */ 416 if ((http_port == 80) && geteuid()) { 417 http_port = 1080; 418 } 419 420 /* Do we really have rights in the html directory? */ 421 if (fetch_mode == NULL) { 422 if (chdir(homedir)) { 423 perror("chdir"); 424 puts(homedir); 425 exit(1); 426 } 427 } 428 429 /* Create log file if it doesn't exit */ 430 if ((access(logfile,W_OK)) && daemonize) { 431 ld = open (logfile,O_WRONLY); 432 chmod (logfile,00600); 433 close(ld); 434 } 435 436 init_servconnection(); 437 438 if (verbose) { 439 printf("Server started with options \n"); 440 printf("port: %d\n",http_port); 441 if (fetch_mode == NULL) printf("html home: %s\n",homedir); 442 if (daemonize) printf("logfile: %s\n",logfile); 443 } 444 445 /* httpd is spawned */ 446 if (daemonize) { 447 if ((server_pid = fork()) != 0) { 448 wait3(0,WNOHANG,0); 449 if (verbose) printf("pid: %d\n",server_pid); 450 exit(0); 451 } 452 wait3(0,WNOHANG,0); 453 } 454 455 if (fetch_mode == NULL) setpgrp(0,httpd_group); 456 457 /* How many connections do you want? 458 * Keep this lower than the available number of processes 459 */ 460 if (listen(http_sock,15) < 0) exit(1); 461 462 label: 463 wait_connection(); 464 465 if (fork()) { 466 wait3(0,WNOHANG,0); 467 close(con_sock); 468 goto label; 469 } 470 471 http_request(); 472 473 wait3(0,WNOHANG,0); 474 exit(0); 475} 476 477 478char * 479adate(void) 480{ 481 static char out[50]; 482 time_t now; 483 struct tm *t; 484 time(&now); 485 t = localtime(&now); 486 sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d", 487 t->tm_hour, t->tm_min, t->tm_sec, 488 t->tm_mday, t->tm_mon+1, t->tm_year ); 489 return out; 490} 491