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