simple_httpd.c revision 113129
1177633Sdfr/*- 2177633Sdfr * Simple_HTTPd v1.1 - a very small, barebones HTTP server 3177633Sdfr * 4177633Sdfr * Copyright (c) 1998-1999 Marc Nicholas <marc@netstor.com> 5177633Sdfr * All rights reserved. 6177633Sdfr * 7177633Sdfr * Major rewrite by William Lloyd <wlloyd@slap.net> 8177633Sdfr * 9177633Sdfr * Redistribution and use in source and binary forms, with or without 10177633Sdfr * modification, are permitted provided that the following conditions 11177633Sdfr * are met: 12177633Sdfr * 1. Redistributions of source code must retain the above copyright 13177633Sdfr * notice, this list of conditions and the following disclaimer. 14177633Sdfr * 2. Redistributions in binary form must reproduce the above copyright 15177633Sdfr * notice, this list of conditions and the following disclaimer in the 16177633Sdfr * documentation and/or other materials provided with the distribution. 17177633Sdfr * 18177633Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19177633Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20177633Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21177633Sdfr * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22177633Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23177633Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24177633Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25177633Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26177633Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27177633Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28177633Sdfr * SUCH DAMAGE. 29177633Sdfr * 30177633Sdfr * $FreeBSD: head/release/picobsd/tinyware/simple_httpd/simple_httpd.c 113129 2003-04-05 17:15:38Z dwmalone $ 31177633Sdfr */ 32177633Sdfr 33177633Sdfr#include <sys/stat.h> 34177633Sdfr#include <sys/time.h> 35177633Sdfr#include <sys/types.h> 36177633Sdfr#include <sys/socket.h> 37177633Sdfr#include <sys/wait.h> 38177633Sdfr#include <netinet/in.h> 39177633Sdfr#include <arpa/inet.h> 40177633Sdfr 41177633Sdfr#include <fcntl.h> 42177633Sdfr#include <netdb.h> 43177633Sdfr#include <signal.h> 44177633Sdfr#include <stdio.h> 45177633Sdfr#include <stdlib.h> 46177633Sdfr#include <string.h> 47177633Sdfr#include <time.h> 48177633Sdfr#include <unistd.h> 49177633Sdfr 50177633Sdfrint http_port = 80; 51177633Sdfrint daemonize = 1; 52177633Sdfrint verbose = 0; 53177633Sdfrint http_sock, con_sock; 54177633Sdfr 55177633Sdfrconst char *fetch_mode = NULL; 56177685Sdfrchar homedir[100]; 57177633Sdfrchar logfile[80]; 58177633Sdfrchar *adate(void); 59177633Sdfrvoid init_servconnection(void); 60177633Sdfrvoid http_date(void); 61177633Sdfrvoid http_output(const char *html); 62177633Sdfrvoid http_request(void); 63177633Sdfrvoid log_line(char *req); 64177633Sdfrvoid wait_connection(void); 65177633Sdfr 66177633Sdfrstruct hostent *hst; 67177633Sdfrstruct sockaddr_in source; 68177633Sdfr 69177633Sdfr/* HTTP basics */ 70177633Sdfrstatic char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r"; 71177633Sdfr 72177633Sdfrstatic char http_200[] = "HTTP/1.0 200 OK\r"; 73177633Sdfr 74177633Sdfr/* Two parts, HTTP Header and then HTML */ 75177633Sdfrstatic const char *http_404[2] = 76177633Sdfr {"HTTP/1.0 404 Not found\r\n", 77177633Sdfr"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\ 78177633SdfrNot found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n" 79177633Sdfr}; 80177633Sdfr 81177633Sdfrstatic const char *http_405[2] = 82177633Sdfr {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n", 83177633Sdfr"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\ 84177633SdfrThis server only supports GET and HEAD requests.\n</BODY></HTML>\r\n" 85177633Sdfr}; 86177633Sdfr 87177633Sdfr/* 88177633Sdfr * Only called on initial invocation 89177633Sdfr */ 90177633Sdfrvoid 91177633Sdfrinit_servconnection(void) 92177633Sdfr{ 93177633Sdfr struct sockaddr_in server; 94177633Sdfr 95177633Sdfr /* Create a socket */ 96177633Sdfr http_sock = socket(AF_INET, SOCK_STREAM, 0); 97177633Sdfr if (http_sock < 0) { 98177633Sdfr perror("socket"); 99177633Sdfr exit(1); 100177633Sdfr } 101177633Sdfr server.sin_family = AF_INET; 102177633Sdfr server.sin_port = htons(http_port); 103177633Sdfr server.sin_addr.s_addr = INADDR_ANY; 104177633Sdfr if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) { 105177633Sdfr perror("bind socket"); 106177633Sdfr exit(1); 107177633Sdfr } 108177633Sdfr if (verbose) printf("simple_httpd:%d\n",http_port); 109177633Sdfr} 110177633Sdfr 111177633Sdfr/* 112177633Sdfr * Wait here until we see an incoming http request 113177633Sdfr */ 114177633Sdfrvoid 115177633Sdfrwait_connection(void) 116177633Sdfr{ 117177633Sdfr socklen_t lg; 118177633Sdfr 119177633Sdfr lg = sizeof(struct sockaddr_in); 120177633Sdfr 121177633Sdfr con_sock = accept(http_sock, (struct sockaddr *) & source, &lg); 122177633Sdfr if (con_sock <= 0) { 123177633Sdfr perror("accept"); 124177633Sdfr exit(1); 125177633Sdfr } 126177633Sdfr} 127177633Sdfr 128177633Sdfr/* 129177633Sdfr * Print timestamp for HTTP HEAD and GET 130177633Sdfr */ 131177633Sdfrvoid 132177633Sdfrhttp_date(void) 133177633Sdfr{ 134177633Sdfr time_t tl; 135177633Sdfr char buff[50]; 136177633Sdfr 137177633Sdfr tl = time(NULL); 138177633Sdfr strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl)); 139177633Sdfr write(con_sock, buff, strlen(buff)); 140177633Sdfr /* return(buff); */ 141177633Sdfr} 142177633Sdfr 143177633Sdfr/* 144177633Sdfr * 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