This was part of an assignment for a Computer Networks course. It implemente GET and conditional GET. Supports persistent connections.
1 // Copyright (c) 2008, Luis Rei (http://luisrei.com <luis.rei@gmail.com>)
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 // * Redistributions of source code must retain the above copyright notice,
8 // this list of conditions and the following disclaimer.
9 // * Redistributions in binary form must reproduce the above copyright
10 // notice, this list of conditions and the following disclaimer in the
11 // documentation and/or other materials provided with the distribution.
12 // * Neither the name of the <ORGANIZATION> nor the names of its
13 // contributors may be used to endorse or promote products derived from
14 // this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 // POSSIBILITY OF SUCH DAMAGE.
27
28
29
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <sys/stat.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
35 #include <sys/wait.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <getopt.h>
39 #include <time.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <signal.h>
45 #include <netdb.h>
46 #include <strings.h>
47
48
49 #define MAXLEN 1024
50 #define TERMINATOR1 "\xD\xA\xD\xA" /*CRLFCRLF - separates header from msg*/
51 #define TERMINATOR2 "\n\n" /*apparently this is supposed to work too*/
52 #define SEP " \xD\xA\n\0" /*this is used for string processing*/
53 #define BACKLOG 10
54 #define DEFAULT_PORT 80
55 #define DEFAULT_ROOT "/var/www/data"
56 #define SERVER "reihttpd/0.1.0\n"
57
58
59 #define R_BADREQUEST "<html><head>\n\
60 <title>400 Bad Request</title>\n\
61 </head><body>\n\
62 <h1>Bad Request</h1>\n\
63 <p>Your browser sent a request that this server could not understand.<br />\n\
64 </p>\n\
65 </body></html>\n"
66
67 #define R_FORBIDDEN "<html><head>\n\
68 <title>403 Forbidden</title>\n\
69 </head><body>\n\
70 <h1>Forbidden</h1>\n\
71 <p>You don't have permission to access this resource.</p>\n\
72 </body></html>\n"
73
74 #define R_NOTFOUND "<html><head>\n\
75 <title>404 Not Found</title>\n\
76 </head><body>\n\
77 <h1>Not Found</h1>\n\
78 <p>The requested URL was not found on this server.</p>\n\
79 </body></html>\n"
80
81 #define R_NOTIMPLEMENTED "<html><head>\n\
82 <title>501 Not Implemented</title>\n\
83 </head><body>\n\
84 <h1>Not Implemented</h1>\n\
85 <p>The method you tried to use is not supported by this server.</p>\n\
86 </body></html>\n"
87
88 #define R_INTERNAL "<html><head>\n\
89 <title>500 Internal Server Error</title>\n\
90 </head><body>\n\
91 <h1>Internal Server Error</h1>\n\
92 <p>Something is broken. Try going back to where you came from.</p>\n\
93 </body></html>\n"
94
95 typedef enum _code {
96 C_OK = 202,
97 C_NOTMOD = 304,
98 C_BADREQUEST = 400,
99 C_FORBIDDEN = 403,
100 C_NOTFOUND = 404,
101 C_INTERNAL = 500,
102 C_NOTIMPLEMENTED = 501
103 } code;
104
105 int port;
106 char *root;
107
108
109 int process_request(int sockfd, char *buf, int bytes) {
110 struct stat st;
111 struct tm since_tm, file_tm;
112 code rcode;
113 time_t now, since_tt, file_tt;
114 size_t fsize;
115 int file_des;
116 char *line;
117 char *request, *location, *protocol, *host, *since, *connection, *rstr;
118 char *args[MAXLEN];
119 char rbuf[MAXLEN];
120 char temp[MAXLEN];
121 char headers[MAXLEN];
122 int ret, ii, argc, rbytes, major, minor;
123 int COND_FLAG;
124
125
126 printf("++++++++++++++++++++++ REQUEST ++++++++++++++++++++++++++++\n");
127 printf("%s", buf);
128 printf("---------------------- REQUEST ----------------------------\n");
129
130
131 argc = 0;
132 line = strtok(buf, "\n");
133 do {
134 args[argc] = (char *) malloc(MAXLEN);
135 if (args[argc] == NULL) {
136 perror("malloc");
137 exit(0);
138 }
139 strncpy(args[argc], line, MAXLEN-1);
140
141 argc++;
142 } while ((line = strtok(NULL, "\n")) != NULL);
143
144 /* Parse the GET request */
145 request = strtok(args[0], SEP);
146 location = strtok(NULL, SEP);
147 protocol = strtok(NULL, SEP);
148
149
150 /*check the protocol used, if < HTTP/1.1 it's a non-persistent connection*/
151 major = minor = 0;
152 if (protocol == NULL)
153 ret = -1; /*protocol not specified*/
154 else {
155 strncpy(temp, protocol, MAXLEN-1);
156 line = strtok(temp, "/");
157 if (strcmp(temp, "HTTP") != 0)
158 ret = -1; /*protocol not HTTP*/
159 else {
160 line = strtok(NULL, ".");
161 major = atoi(line);
162 if(major < 1)
163 ret = -1; /*< HTTP/1.0*/
164 else {
165 line = strtok(NULL, "\0");
166 minor = atoi(line);
167 if(major == 1 && minor == 0)
168 ret = -1; /*HTTP/1.0*/
169 }
170 }
171 }
172
173 /*method*/
174 if (strcmp(request, "GET") == 0) {
175 if (strcmp(location, "/") == 0)
176 location = "index.html";
177 else if(*location == '/')
178 location++; /*skip forward slash */
179 else
180 exit(0);
181
182
183 file_des = open(location, O_RDONLY);
184 if (file_des < 0) {
185 if (errno == EACCES)
186 rcode = C_FORBIDDEN;
187 else
188 rcode = C_NOTFOUND; /*as far as I care*/
189 }
190 else
191 rcode = C_OK;
192
193 /* Parse Headers */
194 connection = NULL;
195 host = NULL;
196 since = NULL;
197 COND_FLAG = 0;
198
199 for (ii = 1; ii < argc; ii++) {
200 line = strtok(args[ii], ": ");
201 if (line == NULL) {
202 printf("Line is null.\n");
203 continue;
204 }
205
206 /*host*/
207 else if (strcmp(line, "Host") == 0)
208 host = strtok(NULL, " ");
209
210 /*If-Modified-Since*/
211 else if(strcmp(line, "If-Modified-Since") == 0) {
212 since = strtok(NULL, "\n");
213 COND_FLAG = 1;
214 if(since != NULL)
215 since++; /*skip whitespace*/
216 else {
217 COND_FLAG = 0;
218 continue;
219 }
220
221 if (since[3] == ',') { /*Sun, 06 Nov 1994 08:49:37 GMT*/
222 rstr = strptime(since, "%a, %d %b %Y %T GMT", &since_tm);
223 if(rstr == NULL) {
224 COND_FLAG = 0;
225 }
226 }
227 else if (since[6] == ',') { /*Sunday, 06-Nov-94 08:49:37 GMT*/
228 rstr = strptime(since, "%a, %d-%b-%y %T GMT", &since_tm);
229 if(rstr == NULL) {
230 COND_FLAG = 0;
231 }
232 }
233 else { /*Sun Nov 6 08:49:37 1994*/
234 rstr = strptime(since, "%a %b %d %T %Y", &since_tm);
235 if(rstr == NULL) {
236 COND_FLAG = 0;
237 }
238 }
239 if (COND_FLAG) {
240 since_tt = mktime(&since_tm);
241 if (time(NULL) < since_tt)
242 COND_FLAG = 0; /*this is not the future!*/
243 }
244 }
245
246 /*Connection*/
247 else if(strcmp(line, "Connection") == 0)
248 connection = strtok(NULL, " ");
249
250 /*unknown*/
251 else
252 continue;
253 }
254
255 /*if protocol >= HTTP/1.1 Host must be specified*/
256 if ((major == 1 && minor >= 1) || major > 1)
257 if (host == NULL) {
258 rcode = C_BADREQUEST;
259 }
260 }
261 else
262 rcode = C_NOTIMPLEMENTED;
263
264
265 /*build Header*/
266
267 /*get current time*/
268 now = time(NULL);
269
270 /*message type*/
271 if (rcode == C_OK) {
272 /*get size and last modified date*/
273 fstat(file_des, &st);
274
275
276 if (COND_FLAG) {
277 /* convert file time to a time_t value for comparison*/
278 /*this way of doing is lame but it works*/
279 strftime(temp, MAXLEN-1, "%a, %d %b %Y %T GMT%n",
280 gmtime(&st.st_mtimespec.tv_sec));
281 strptime(temp, "%a, %d %b %Y %T GMT", &file_tm);
282 file_tt = mktime(&file_tm);
283
284 /*compare*/
285 if(file_tt <= since_tt) { /*the file has not been modified*/
286 rcode = C_NOTMOD;
287 fsize = 0;
288 }
289 }
290 }
291
292 switch(rcode) {
293 case C_OK:
294 fsize = st.st_size;
295 sprintf(headers, "HTTP/1.1 %d OK\n", C_OK);
296 break;
297 case C_NOTMOD:
298 sprintf(headers, "HTTP/1.1 %d Not Modified\n", C_NOTMOD);
299 break;
300 case C_FORBIDDEN:
301 strcpy(rbuf, R_FORBIDDEN);
302 sprintf(headers, "HTTP/1.1 %d Forbidden\n", C_FORBIDDEN);
303 break;
304 case C_NOTFOUND:
305 strcpy(rbuf, R_NOTFOUND);
306 sprintf(headers, "HTTP/1.1 %d Not Found\n", C_NOTFOUND);
307 break;
308 case C_NOTIMPLEMENTED:
309 strcpy(rbuf, R_NOTIMPLEMENTED);
310 sprintf(headers, "HTTP/1.1 %d Not Implemented\n",
311 C_NOTIMPLEMENTED);
312 break;
313 case C_BADREQUEST:
314 strcpy(rbuf, R_BADREQUEST);
315 sprintf(headers, "HTTP/1.1 %d Bad Request\n", C_BADREQUEST);
316 break;
317 default: /*internal error*/
318 strcpy(rbuf, R_INTERNAL);
319 sprintf(headers, "HTTP/1.1 %d Internal Server Error\n",
320 C_INTERNAL);
321 break;
322 }
323
324 if(rcode != C_OK && rcode != C_NOTMOD) {
325 fsize = strlen(rbuf);
326 ret = -1;
327 }
328
329
330
331 /*example: Date: Mon, 05 May 2008 10:06:01 GMT*/
332 strcat(headers, "Date: ");
333 strftime(temp, MAXLEN-1, "%a, %d %b %Y %T GMT%n", gmtime(&now));
334 strcat(headers, temp);
335
336 /*example: Server: Apache/2.2.8 (Unix) [...]*/
337 strcat(headers, "Server: ");
338 strcat(headers, SERVER);
339
340 if (rcode == C_OK) {
341 /*example: Last-Modified: Mon, 05 May 2008 10:06:01 GMT*/
342 strcat(headers, "Last-Modified: ");
343 strftime(temp, MAXLEN-1, "%a, %d %b %Y %T GMT%n",
344 gmtime(&st.st_mtimespec.tv_sec));
345 strcat(headers, temp);
346 }
347
348 /*example: Content-Length: 5754 */
349 strcat(headers, "Content-Length: ");
350 sprintf(temp, "%lu\n", (unsigned long) fsize);
351 strcat(headers, temp);
352
353 /*TODO: example: Content-Type: text/html*/
354
355 /*add a newline*/
356 strcat(headers, "\n");
357
358
359 /*send header fields and data*/
360 printf("++++++++++++++++++++++ REPLY +++++++++++++++++++++++++++\n");
361 printf("header:\n%s", headers);
362 send(sockfd, headers, strlen(headers), 0);
363 if (rcode == C_OK)
364 while ((rbytes = read(file_des, rbuf, MAXLEN-1)) > 0)
365 send(sockfd, rbuf, rbytes, 0);
366 else
367 send(sockfd, rbuf, fsize, 0);
368 printf("message:\n%s\n", rbuf);
369 printf("---------------------- REPLY ---------------------------\n");
370
371
372 if (connection != NULL)
373 if (strncmp(connection, "close", 5) == 0)
374 ret = -1; /*client asked to close the connection*/
375
376 /*free dynamically allocated memory*/
377 for (ii=0; ii < argc; ii++)
378 free(args[ii]);
379
380
381 return ret;
382 }
383
384
385 int main(int argc, char** argv){
386
387 int sockfd, newsockfd;
388 unsigned int cli_size;
389 struct sockaddr_in serv_addr, cli_addr;
390 int childpid;
391 int opt, long_index;
392 char buf[MAXLEN], request[MAXLEN];
393 int bytes, ret;
394
395 static const char *opt_string = "hp:r:";
396 static const struct option long_opts[] = {
397 {"Port", required_argument, NULL, 'p'},
398 {"Root", required_argument, NULL, 'r'},
399 {"help", no_argument, NULL, 'h'},
400 {NULL, no_argument, NULL, 0}
401 };
402
403 /*set defaults*/
404 port = DEFAULT_PORT;
405 root = DEFAULT_ROOT;
406
407 /*command-line arguments*/
408 while ((opt = getopt_long(argc, argv, opt_string, long_opts, &long_index))
409 != -1) {
410 switch (opt) {
411 case 'p':
412 port = atoi(optarg);
413 break;
414
415 case 'r':
416 root = optarg;
417 break;
418
419 case 'h':
420 //display_usage();
421 break;
422
423 case '?':
424 break;
425
426 default:
427 exit(0);
428 break;
429 }
430 }
431
432
433
434 /*TODO: read a config file, close the config file*/
435 /*TODO: if root, map UID/GID of low-priviledge acount*/
436
437 /*open a TCP socket*/
438 if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
439 perror("socket()");
440 exit(0);
441 }
442 /*bind the local address so that a client can connect*/
443 bzero((char*)&serv_addr,sizeof(serv_addr));
444 serv_addr.sin_family = AF_INET;
445 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
446 /*server TCP port must be network byte ordered */
447 serv_addr.sin_port = htons(port);
448
449 if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
450 perror("bind()");
451 exit(0);
452 }
453
454 /*TODO: connect to syslogd*/
455
456 /*preload (possibly) dynamically lodaded objects*/
457 gethostbyname("localhost");
458
459 /*change directory*/
460 if(chdir(root) < 0) {
461 perror("chdir()");
462 exit(0);
463 }
464 if(geteuid() == 0) { /*root can use chroot*/
465 if(chroot("./") < 0) {
466 perror("chroot()");
467 exit(0);
468 }
469 }
470
471
472 /*TODO: if root, change to predefined low-priv user via setXXuid
473 cf "Setuid Demystified", Chen & Wagner, Usenix 2002*/
474
475 listen(sockfd, BACKLOG);
476
477 /*main loop*/
478 while(1) {
479 /*wait for a connection request (concurrent server)*/
480 cli_size = sizeof(cli_addr);
481 newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_size);
482
483 if (newsockfd < 0) {
484 perror("accept()");
485 exit(0);
486 }
487
488 /*fork the server to handle the connections*/
489 if ((childpid = fork()) < 0) {
490 perror("fork()");
491 exit(0);
492 }
493 else if (childpid == 0) { /*child process - process client requests*/
494 close(sockfd);
495
496 ret = 0;
497 while (ret >= 0) {
498 request[0] = '\0';
499 while ((strstr(request, TERMINATOR1) == NULL) &&
500 (strstr(request, TERMINATOR2) == NULL)) {
501 bytes = recv(newsockfd, buf, MAXLEN-1, 0);
502 buf[bytes] = '\0';
503 strncat(request, buf, bytes);
504 }
505
506 ret = process_request(newsockfd, request, strlen(request));
507 }
508
509 exit(0);
510 }
511 else { /*parent process - waits the child process to terminate*/
512 if (waitpid(childpid, NULL, 0) != childpid)
513 perror("waitpid");
514 close(newsockfd);
515 }
516 }
517 }
Pages : 1