simple_httpd.c revision 94132
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 94132 2002-04-07 17:16:28Z 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 */ 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, i; 195 int cmd = 0; 196 char *p, *par; 197 char *filename, *c; 198 struct stat file_status; 199 char req[1024]; 200 char buff[8192]; 201 202 lg = read(con_sock, req, 1024); 203 204 if (p=strstr(req,"\n")) *p=0; 205 if (p=strstr(req,"\r")) *p=0; 206 207 log_line(req); 208 209 c = strtok(req, " "); 210 211 /* Error msg if request is nothing */ 212 if (c == NULL) { 213 http_output(http_404[0]); 214 http_output(http_404[1]); 215 goto end_request; 216 } 217 218 if (strncmp(c, "GET", 3) == 0) cmd = 1; 219 if (strncmp(c, "HEAD", 4) == 0) cmd = 2; 220 221 /* Do error msg for any other type of request */ 222 if (cmd == 0) { 223 http_output(http_405[0]); 224 http_output(http_405[1]); 225 goto end_request; 226 } 227 228 filename = strtok(NULL, " "); 229 230 c = strtok(NULL, " "); 231 if (fetch_mode[0] != NULL) strcpy(filename,fetch_mode); 232 if (filename == NULL || 233 strlen(filename)==1) filename="/index.html"; 234 235 while (filename[0]== '/') filename++; 236 237 /* CGI handling. Untested */ 238 if (!strncmp(filename,"cgi-bin/",8)) 239 { 240 par=0; 241 if (par=strstr(filename,"?")) 242 { 243 *par=0; 244 par++; 245 } 246 if (access(filename,X_OK)) goto conti; 247 stat (filename,&file_status); 248 if (setuid(file_status.st_uid)) return(0); 249 if (seteuid(file_status.st_uid)) return(0); 250 if (!fork()) 251 { 252 close(1); 253 dup(con_sock); 254 //printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n"); 255 printf("HTTP/1.0 200 OK\r\n"); 256 /* Plug in environment variable, others in log_line */ 257 putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD"); 258 259 execlp (filename,filename,par,(char *)0); 260 } 261 wait(&i); 262 return(0); 263 } 264 conti: 265 if (filename == NULL) { 266 http_output(http_405[0]); 267 http_output(http_405[1]); 268 goto end_request; 269 } 270 /* End of CGI handling */ 271 272 /* Reject any request with '..' in it, bad hacker */ 273 c = filename; 274 while (*c != '\0') 275 if (c[0] == '.' && c[1] == '.') { 276 http_output(http_404[0]); 277 http_output(http_404[1]); 278 goto end_request; 279 } else 280 c++; 281 282 /* Open filename */ 283 fd = open(filename, O_RDONLY); 284 if (fd < 0) { 285 http_output(http_404[0]); 286 http_output(http_404[1]); 287 goto end_request; 288 } 289 290 /* Get file status information */ 291 if (fstat(fd, &file_status) < 0) { 292 http_output(http_404[0]); 293 http_output(http_404[1]); 294 goto end_request; 295 } 296 297 /* Is it a regular file? */ 298 if (!S_ISREG(file_status.st_mode)) { 299 http_output(http_404[0]); 300 http_output(http_404[1]); 301 goto end_request; 302 } 303 304 /* Past this point we are serving either a GET or HEAD */ 305 /* Print all the header info */ 306 http_output(http_200); 307 http_output(httpd_server_ident); 308 http_date(); 309 310 sprintf(buff, "Content-length: %d\r\n", file_status.st_size); 311 312 if (strstr(filename,".txt")) { 313 strcpy(buff,"Content-type: text/plain\r\n"); 314 } else if (strstr(filename,".html") || strstr(filename,".htm")) { 315 strcpy(buff,"Content-type: text/html\r\n"); 316 } else if (strstr(filename,".gif")) { 317 strcpy(buff,"Content-type: image/gif\r\n"); 318 } else if (strstr(filename,".jpg")) { 319 strcpy(buff,"Content-type: image/jpeg\r\n"); 320 } else { 321 /* Take a guess at content if we don't have something already */ 322 strcpy(buff,"Content-type: "); 323 strcat(buff,strstr(filename,".")+1); 324 strcat(buff,"\r\n"); 325 } 326 write(con_sock, buff, strlen(buff)); 327 328 strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime)); 329 write(con_sock, buff, strlen(buff)); 330 331 /* Send data only if GET request */ 332 if (cmd == 1) { 333 while (lg = read(fd, buff, 8192)) 334 write(con_sock, buff, lg); 335 } 336 337end_request: 338 close(fd); 339 close(con_sock); 340 341} 342 343/* 344 * Simple httpd server for use in PicoBSD or other embedded application. 345 * Should satisfy simple httpd needs. For more demanding situations 346 * apache is probably a better (but much larger) choice. 347 */ 348main(int argc, char *argv[]) 349{ 350 extern char *optarg; 351 extern int optind; 352 int ch, ld; 353 int httpd_group = 65534; 354 pid_t server_pid; 355 356 /* Default for html directory */ 357 strcpy (homedir,getenv("HOME")); 358 if (!geteuid()) strcpy (homedir,"/httphome"); 359 else strcat (homedir,"/httphome"); 360 361 /* Defaults for log file */ 362 if (geteuid()) { 363 strcpy(logfile,getenv("HOME")); 364 strcat(logfile,"/"); 365 strcat(logfile,"jhttp.log"); 366 } else 367 strcpy(logfile,"/var/log/jhttpd.log"); 368 369 /* Parse command line arguments */ 370 while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1) 371 switch (ch) { 372 case 'd': 373 strcpy(homedir,optarg); 374 break; 375 case 'f': 376 daemonize = 0; 377 verbose = 1; 378 strcpy(fetch_mode,optarg); 379 break; 380 case 'g': 381 httpd_group = atoi(optarg); 382 break; 383 case 'l': 384 strcpy(logfile,optarg); 385 break; 386 case 'p': 387 http_port = atoi(optarg); 388 break; 389 case 'v': 390 verbose = 1; 391 break; 392 case 'D': 393 daemonize = 0; 394 break; 395 case '?': 396 case 'h': 397 default: 398 printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n"); 399 exit(1); 400 /* NOTREACHED */ 401 } 402 403 /* Not running as root and no port supplied, assume 1080 */ 404 if ((http_port == 80) && geteuid()) { 405 http_port = 1080; 406 } 407 408 /* Do we really have rights in the html directory? */ 409 if (fetch_mode[0] == NULL) { 410 if (chdir(homedir)) { 411 perror("chdir"); 412 puts(homedir); 413 exit(1); 414 } 415 } 416 417 /* Create log file if it doesn't exit */ 418 if ((access(logfile,W_OK)) && daemonize) { 419 ld = open (logfile,O_WRONLY); 420 chmod (logfile,00600); 421 close(ld); 422 } 423 424 init_servconnection(); 425 426 if (verbose) { 427 printf("Server started with options \n"); 428 printf("port: %d\n",http_port); 429 if (fetch_mode[0] == NULL) printf("html home: %s\n",homedir); 430 if (daemonize) printf("logfile: %s\n",logfile); 431 } 432 433 /* httpd is spawned */ 434 if (daemonize) { 435 if (server_pid = fork()) { 436 wait3(0,WNOHANG,0); 437 if (verbose) printf("pid: %d\n",server_pid); 438 exit(0); 439 } 440 wait3(0,WNOHANG,0); 441 } 442 443 if (fetch_mode[0] == NULL) setpgrp(0,httpd_group); 444 445 /* How many connections do you want? 446 * Keep this lower than the available number of processes 447 */ 448 if (listen(http_sock,15) < 0) exit(1); 449 450 label: 451 wait_connection(); 452 453 if (fork()) { 454 wait3(0,WNOHANG,0); 455 close(con_sock); 456 goto label; 457 } 458 459 http_request(); 460 461 wait3(0,WNOHANG,0); 462 exit(0); 463} 464 465 466char *adate() 467{ 468 static char out[50]; 469 long now; 470 struct tm *t; 471 time(&now); 472 t = localtime(&now); 473 sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d", 474 t->tm_hour, t->tm_min, t->tm_sec, 475 t->tm_mday, t->tm_mon+1, t->tm_year ); 476 return out; 477} 478