simple_httpd.c revision 113126
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 113126 2003-04-05 15:27:27Z 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 323 if (strstr(filename,".txt")) { 324 strcpy(buff,"Content-type: text/plain\r\n"); 325 } else if (strstr(filename,".html") || strstr(filename,".htm")) { 326 strcpy(buff,"Content-type: text/html\r\n"); 327 } else if (strstr(filename,".gif")) { 328 strcpy(buff,"Content-type: image/gif\r\n"); 329 } else if (strstr(filename,".jpg")) { 330 strcpy(buff,"Content-type: image/jpeg\r\n"); 331 } else { 332 /* Take a guess at content if we don't have something already */ 333 strcpy(buff,"Content-type: "); 334 strcat(buff,strstr(filename,".")+1); 335 strcat(buff,"\r\n"); 336 } 337 write(con_sock, buff, strlen(buff)); 338 339 strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime)); 340 write(con_sock, buff, strlen(buff)); 341 342 /* Send data only if GET request */ 343 if (cmd == 1) { 344 while ((lg = read(fd, buff, 8192)) > 0) 345 write(con_sock, buff, lg); 346 } 347 348end_request2: 349 close(fd); 350end_request: 351 close(con_sock); 352 353} 354 355/* 356 * Simple httpd server for use in PicoBSD or other embedded application. 357 * Should satisfy simple httpd needs. For more demanding situations 358 * apache is probably a better (but much larger) choice. 359 */ 360int 361main(int argc, char *argv[]) 362{ 363 int ch, ld; 364 int httpd_group = 65534; 365 pid_t server_pid; 366 367 /* Default for html directory */ 368 strcpy (homedir,getenv("HOME")); 369 if (!geteuid()) strcpy (homedir,"/httphome"); 370 else strcat (homedir,"/httphome"); 371 372 /* Defaults for log file */ 373 if (geteuid()) { 374 strcpy(logfile,getenv("HOME")); 375 strcat(logfile,"/"); 376 strcat(logfile,"jhttp.log"); 377 } else 378 strcpy(logfile,"/var/log/jhttpd.log"); 379 380 /* Parse command line arguments */ 381 while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1) 382 switch (ch) { 383 case 'd': 384 strcpy(homedir,optarg); 385 break; 386 case 'f': 387 daemonize = 0; 388 verbose = 1; 389 fetch_mode = optarg; 390 break; 391 case 'g': 392 httpd_group = atoi(optarg); 393 break; 394 case 'l': 395 strcpy(logfile,optarg); 396 break; 397 case 'p': 398 http_port = atoi(optarg); 399 break; 400 case 'v': 401 verbose = 1; 402 break; 403 case 'D': 404 daemonize = 0; 405 break; 406 case '?': 407 case 'h': 408 default: 409 printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n"); 410 exit(1); 411 /* NOTREACHED */ 412 } 413 414 /* Not running as root and no port supplied, assume 1080 */ 415 if ((http_port == 80) && geteuid()) { 416 http_port = 1080; 417 } 418 419 /* Do we really have rights in the html directory? */ 420 if (fetch_mode == NULL) { 421 if (chdir(homedir)) { 422 perror("chdir"); 423 puts(homedir); 424 exit(1); 425 } 426 } 427 428 /* Create log file if it doesn't exit */ 429 if ((access(logfile,W_OK)) && daemonize) { 430 ld = open (logfile,O_WRONLY); 431 chmod (logfile,00600); 432 close(ld); 433 } 434 435 init_servconnection(); 436 437 if (verbose) { 438 printf("Server started with options \n"); 439 printf("port: %d\n",http_port); 440 if (fetch_mode == NULL) printf("html home: %s\n",homedir); 441 if (daemonize) printf("logfile: %s\n",logfile); 442 } 443 444 /* httpd is spawned */ 445 if (daemonize) { 446 if ((server_pid = fork()) != 0) { 447 wait3(0,WNOHANG,0); 448 if (verbose) printf("pid: %d\n",server_pid); 449 exit(0); 450 } 451 wait3(0,WNOHANG,0); 452 } 453 454 if (fetch_mode == NULL) setpgrp(0,httpd_group); 455 456 /* How many connections do you want? 457 * Keep this lower than the available number of processes 458 */ 459 if (listen(http_sock,15) < 0) exit(1); 460 461 label: 462 wait_connection(); 463 464 if (fork()) { 465 wait3(0,WNOHANG,0); 466 close(con_sock); 467 goto label; 468 } 469 470 http_request(); 471 472 wait3(0,WNOHANG,0); 473 exit(0); 474} 475 476 477char * 478adate(void) 479{ 480 static char out[50]; 481 time_t now; 482 struct tm *t; 483 time(&now); 484 t = localtime(&now); 485 sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d", 486 t->tm_hour, t->tm_min, t->tm_sec, 487 t->tm_mday, t->tm_mon+1, t->tm_year ); 488 return out; 489} 490