simple_httpd.c revision 50479
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 50479 1999-08-28 01:35:59Z peter $ 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 55char fetch_mode[100]; 56char homedir[100]; 57char logfile[80]; 58char *adate(); 59 60struct hostent *hst; 61struct sockaddr_in source; 62 63/* HTTP basics */ 64static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r"; 65 66static char http_200[] = "HTTP/1.0 200 OK\r"; 67 68/* Two parts, HTTP Header and then HTML */ 69static char *http_404[2] = 70 {"HTTP/1.0 404 Not found\r\n", 71"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\ 72Not found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n" 73}; 74 75static char *http_405[2] = 76 {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n", 77"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\ 78This server only supports GET and HEAD requests.\n</BODY></HTML>\r\n" 79}; 80 81/* 82 * Only called on initial invocation 83 */ 84void 85init_servconnection(void) 86{ 87 struct sockaddr_in server; 88 89 /* Create a socket */ 90 http_sock = socket(AF_INET, SOCK_STREAM, 0); 91 if (http_sock < 0) { 92 perror("socket"); 93 exit(1); 94 } 95 server.sin_family = AF_INET; 96 server.sin_port = htons(http_port); 97 server.sin_addr.s_addr = INADDR_ANY; 98 if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) { 99 perror("bind socket"); 100 exit(1); 101 } 102 if (verbose) printf("simple_httpd\n",http_port); 103} 104 105/* 106 * Wait here until we see an incoming http request 107 */ 108wait_connection(void) 109{ 110 int lg; 111 112 lg = sizeof(struct sockaddr_in); 113 114 con_sock = accept(http_sock, (struct sockaddr *) & source, &lg); 115 if (con_sock <= 0) { 116 perror("accept"); 117 exit(1); 118 } 119} 120 121/* 122 * Print timestamp for HTTP HEAD and GET 123 */ 124http_date() 125{ 126 time_t tl; 127 char buff[50]; 128 129 tl = time(NULL); 130 strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl)); 131 write(con_sock, buff, strlen(buff)); 132 //return(buff); 133} 134 135/* 136 * Send data to the open socket 137 */ 138http_output(char *html) 139{ 140 write(con_sock, html, strlen(html)); 141 write(con_sock, "\r\n", 2); 142} 143 144 145/* 146 * Create and write the log information to file 147 * Log file format is one line per entry 148 */ 149log_line(char *req) 150{ 151 char log_buff[256]; 152 char msg[1024]; 153 char env_host[80], env_addr[80]; 154 long addr; 155 FILE *log; 156 157 strcpy(log_buff,inet_ntoa(source.sin_addr)); 158 sprintf(env_addr, "REMOTE_ADDR=%s",log_buff); 159 160 addr=inet_addr(log_buff); 161 162 strcpy(msg,adate()); 163 strcat(msg," "); 164 hst=gethostbyaddr((char*) &addr, 4, AF_INET); 165 166 /* If DNS hostname exists */ 167 if (hst) { 168 strcat(msg,hst->h_name); 169 sprintf(env_host, "REMOTE_HOST=%s",hst->h_name); 170 } 171 strcat(msg," ("); 172 strcat(msg,log_buff); 173 strcat(msg,") "); 174 strcat(msg,req); 175 176 if (daemonize) { 177 log=fopen(logfile,"a"); 178 fprintf(log,"%s\n",msg); 179 fclose(log); 180 } else 181 printf("%s\n",msg); 182 183 /* This is for CGI scripts */ 184 putenv(env_addr); 185 putenv(env_host); 186} 187 188/* 189 * We have a connection. Identify what type of request GET, HEAD, CGI, etc 190 * and do what needs to be done 191 */ 192http_request() 193{ 194 int fd, lg, ld, i; 195 int cmd = 0; 196 int http1 = 0; 197 char *p, *par; 198 char *filename, *c; 199 struct stat file_status; 200 char req[1024]; 201 char msg[1024]; 202 char buff[8192]; 203 204 lg = read(con_sock, req, 1024); 205 206 if (p=strstr(req,"\n")) *p=0; 207 if (p=strstr(req,"\r")) *p=0; 208 209 log_line(req); 210 211 c = strtok(req, " "); 212 213 /* Error msg if request is nothing */ 214 if (c == NULL) { 215 http_output(http_404[0]); 216 http_output(http_404[1]); 217 goto end_request; 218 } 219 220 if (strncmp(c, "GET", 3) == 0) cmd = 1; 221 if (strncmp(c, "HEAD", 4) == 0) cmd = 2; 222 223 /* Do error msg for any other type of request */ 224 if (cmd == 0) { 225 http_output(http_405[0]); 226 http_output(http_405[1]); 227 goto end_request; 228 } 229 230 filename = strtok(NULL, " "); 231 232 c = strtok(NULL, " "); 233 if (fetch_mode[0] != NULL) strcpy(filename,fetch_mode); 234 if (filename == NULL || 235 strlen(filename)==1) filename="/index.html"; 236 237 while (filename[0]== '/') filename++; 238 239 /* CGI handling. Untested */ 240 if (!strncmp(filename,"cgi-bin/",8)) 241 { 242 par=0; 243 if (par=strstr(filename,"?")) 244 { 245 *par=0; 246 par++; 247 } 248 if (access(filename,X_OK)) goto conti; 249 stat (filename,&file_status); 250 if (setuid(file_status.st_uid)) return(0); 251 if (seteuid(file_status.st_uid)) return(0); 252 if (!fork()) 253 { 254 close(1); 255 dup(con_sock); 256 //printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n"); 257 printf("HTTP/1.0 200 OK\r\n"); 258 /* Plug in environment variable, others in log_line */ 259 putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD"); 260 261 execlp (filename,filename,par,0); 262 } 263 wait(&i); 264 return(0); 265 } 266 conti: 267 if (filename == NULL) { 268 http_output(http_405[0]); 269 http_output(http_405[1]); 270 goto end_request; 271 } 272 /* End of CGI handling */ 273 274 /* Reject any request with '..' in it, bad hacker */ 275 c = filename; 276 while (*c != '\0') 277 if (c[0] == '.' && c[1] == '.') { 278 http_output(http_404[0]); 279 http_output(http_404[1]); 280 goto end_request; 281 } else 282 c++; 283 284 /* Open filename */ 285 fd = open(filename, O_RDONLY); 286 if (fd < 0) { 287 http_output(http_404[0]); 288 http_output(http_404[1]); 289 goto end_request; 290 } 291 292 /* Get file status information */ 293 if (fstat(fd, &file_status) < 0) { 294 http_output(http_404[0]); 295 http_output(http_404[1]); 296 goto end_request; 297 } 298 299 /* Is it a regular file? */ 300 if (!S_ISREG(file_status.st_mode)) { 301 http_output(http_404[0]); 302 http_output(http_404[1]); 303 goto end_request; 304 } 305 306 /* Past this point we are serving either a GET or HEAD */ 307 /* Print all the header info */ 308 http_output(http_200); 309 http_output(httpd_server_ident); 310 http_date(); 311 312 sprintf(buff, "Content-length: %d\r\n", file_status.st_size); 313 314 if (strstr(filename,".txt")) { 315 strcpy(buff,"Content-type: text/plain\r\n"); 316 } else if (strstr(filename,".html") || strstr(filename,".htm")) { 317 strcpy(buff,"Content-type: text/html\r\n"); 318 } else if (strstr(filename,".gif")) { 319 strcpy(buff,"Content-type: image/gif\r\n"); 320 } else if (strstr(filename,".jpg")) { 321 strcpy(buff,"Content-type: image/jpeg\r\n"); 322 } else { 323 /* Take a guess at content if we don't have something already */ 324 strcpy(buff,"Content-type: "); 325 strcat(buff,strstr(filename,".")+1); 326 strcat(buff,"\r\n"); 327 } 328 write(con_sock, buff, strlen(buff)); 329 330 strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime)); 331 write(con_sock, buff, strlen(buff)); 332 333 /* Send data only if GET request */ 334 if (cmd == 1) { 335 while (lg = read(fd, buff, 8192)) 336 write(con_sock, buff, lg); 337 } 338 339end_request: 340 close(fd); 341 close(con_sock); 342 343} 344 345/* 346 * Simple httpd server for use in PicoBSD or other embedded application. 347 * Should satisfy simple httpd needs. For more demanding situations 348 * apache is probably a better (but much larger) choice. 349 */ 350main(int argc, char *argv[]) 351{ 352 extern char *optarg; 353 extern int optind; 354 int bflag, ch, fd, ld; 355 int lg; 356 int httpd_group = 65534; 357 pid_t server_pid; 358 359 /* Default for html directory */ 360 strcpy (homedir,getenv("HOME")); 361 if (!geteuid()) strcpy (homedir,"/httphome"); 362 else strcat (homedir,"/httphome"); 363 364 /* Defaults for log file */ 365 if (geteuid()) { 366 strcpy(logfile,getenv("HOME")); 367 strcat(logfile,"/"); 368 strcat(logfile,"jhttp.log"); 369 } else 370 strcpy(logfile,"/var/log/jhttpd.log"); 371 372 /* Parse command line arguments */ 373 while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1) 374 switch (ch) { 375 case 'd': 376 strcpy(homedir,optarg); 377 break; 378 case 'f': 379 daemonize = 0; 380 verbose = 1; 381 strcpy(fetch_mode,optarg); 382 break; 383 case 'g': 384 httpd_group = atoi(optarg); 385 break; 386 case 'l': 387 strcpy(logfile,optarg); 388 break; 389 case 'p': 390 http_port = atoi(optarg); 391 break; 392 case 'v': 393 verbose = 1; 394 break; 395 case 'D': 396 daemonize = 0; 397 break; 398 case '?': 399 case 'h': 400 default: 401 printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n"); 402 exit(1); 403 /* NOTREACHED */ 404 } 405 406 /* Not running as root and no port supplied, assume 1080 */ 407 if ((http_port == 80) && geteuid()) { 408 http_port = 1080; 409 } 410 411 /* Do we really have rights in the html directory? */ 412 if (fetch_mode[0] == NULL) { 413 if (chdir(homedir)) { 414 perror("chdir"); 415 puts(homedir); 416 exit(1); 417 } 418 } 419 420 /* Create log file if it doesn't exit */ 421 if ((access(logfile,W_OK)) && daemonize) { 422 ld = open (logfile,O_WRONLY); 423 chmod (logfile,00600); 424 close(ld); 425 } 426 427 init_servconnection(); 428 429 if (verbose) { 430 printf("Server started with options \n"); 431 printf("port: %d\n",http_port); 432 if (fetch_mode[0] == NULL) printf("html home: %s\n",homedir); 433 if (daemonize) printf("logfile: %s\n",logfile); 434 } 435 436 /* httpd is spawned */ 437 if (daemonize) { 438 if (server_pid = fork()) { 439 wait3(0,WNOHANG,0); 440 if (verbose) printf("pid: %d\n",server_pid); 441 exit(0); 442 } 443 wait3(0,WNOHANG,0); 444 } 445 446 if (fetch_mode[0] == NULL) setpgrp(0,httpd_group); 447 448 /* How many connections do you want? 449 * Keep this lower than the available number of processes 450 */ 451 if (listen(http_sock,15) < 0) exit(1); 452 453 label: 454 wait_connection(); 455 456 if (fork()) { 457 wait3(0,WNOHANG,0); 458 close(con_sock); 459 goto label; 460 } 461 462 http_request(); 463 464 wait3(0,WNOHANG,0); 465 exit(0); 466} 467 468 469char *adate() 470{ 471 static char out[50]; 472 long now; 473 struct tm *t; 474 time(&now); 475 t = localtime(&now); 476 sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d", 477 t->tm_hour, t->tm_min, t->tm_sec, 478 t->tm_mday, t->tm_mon+1, t->tm_year ); 479 return out; 480} 481