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