1 /*-
2 * Simple_HTTPd v1.1 - a very small, barebones HTTP server
3 *
4 * Copyright (c) 1998-1999 Marc Nicholas <[email protected]>
5 * All rights reserved.
6 *
7 * Major rewrite by William Lloyd <[email protected]>
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$
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
51 static int http_port = 80;
52 static int daemonize = 1;
53 static int verbose = 0;
54 static int http_sock, con_sock;
55
56 static const char *fetch_mode = NULL;
57 static char homedir[100];
58 static char logfile[80];
59 static char *adate(void);
60 static void init_servconnection(void);
61 static void http_date(void);
62 static void http_output(const char *html);
63 static void http_request(void);
64 static void log_line(char *req);
65 static void wait_connection(void);
66
67 static struct hostent *hst;
68 static struct sockaddr_in source;
69
70 /* HTTP basics */
71 static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r";
72
73 static char http_200[] = "HTTP/1.0 200 OK\r";
74
75 static const char *default_mime_type = "application/octet-stream";
76
77 static const 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
86 static const int mime_type_max = sizeof(mime_type) / sizeof(mime_type[0]) - 1;
87
88 /* Two parts, HTTP Header and then HTML */
89 static 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>\
92 Not found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n"
93 };
94
95 static 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>\
98 This server only supports GET and HEAD requests.\n</BODY></HTML>\r\n"
99 };
100
101 /*
102 * Only called on initial invocation
103 */
104 static void
init_servconnection(void)105 init_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 */
128 static void
wait_connection(void)129 wait_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 */
145 static void
http_date(void)146 http_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 */
160 static void
http_output(const char * html)161 http_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 */
172 static void
log_line(char * req)173 log_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 */
216 static void
http_request(void)217 http_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
359 end_request2:
360 close(fd);
361 end_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 */
371 int
main(int argc,char * argv[])372 main(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
489 char *
adate(void)490 adate(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