· 6 years ago · Dec 11, 2019, 07:20 PM
1/*
2 * A partial implementation of HTTP/1.0
3 *
4 * This code is mainly intended as a replacement for the book's 'tiny.c' server
5 * It provides a *partial* implementation of HTTP/1.0 which can form a basis for
6 * the assignment.
7 *
8 * @author G. Back for CS 3214 Spring 2018
9 */
10#include <sys/types.h>
11#include <sys/socket.h>
12#include <sys/stat.h>
13#include <string.h>
14#include <stdarg.h>
15#include <stdio.h>
16#include <stdbool.h>
17#include <errno.h>
18#include <unistd.h>
19#include <time.h>
20#include <fcntl.h>
21#include <linux/limits.h>
22#include <jwt.h>
23#include <jansson.h>
24
25#include "http.h"
26#include "hexdump.h"
27#include "socket.h"
28#include "bufio.h"
29
30// Need macros here because of the sizeof
31#define CRLF "\r\n"
32#define STARTS_WITH(field_name, header) \
33 (!strncasecmp(field_name, header, sizeof(header) - 1))
34
35char *server_root; // root from which static files are served
36int token_expiration_time;
37static const char *NEVER_EMBED_A_SECRET_IN_CODE = "supa secret";
38
39/* Parse HTTP request line, setting req_method, req_path, and req_version. */
40static bool
41http_parse_request(struct http_transaction *ta)
42{
43 size_t req_offset;
44 ssize_t len = bufio_readline(ta->client->bufio, &req_offset);
45 if (len < 2) // error, EOF, or less than 2 characters
46 return false;
47
48 char *request = bufio_offset2ptr(ta->client->bufio, req_offset);
49 char *endptr;
50 char *method = strtok_r(request, " ", &endptr);
51 if (method == NULL)
52 return false;
53
54 if (!strcmp(method, "GET"))
55 ta->req_method = HTTP_GET;
56 else if (!strcmp(method, "POST"))
57 ta->req_method = HTTP_POST;
58 else
59 ta->req_method = HTTP_UNKNOWN;
60
61 char *req_path = strtok_r(NULL, " ", &endptr);
62 if (req_path == NULL)
63 return false;
64
65 ta->req_path = bufio_ptr2offset(ta->client->bufio, req_path);
66
67 char *http_version = strtok_r(NULL, CRLF, &endptr);
68 if (http_version == NULL) // would be HTTP 0.9
69 return false;
70
71 if (!strcmp(http_version, "HTTP/1.1"))
72 ta->req_version = HTTP_1_1;
73 else if (!strcmp(http_version, "HTTP/1.0"))
74 ta->req_version = HTTP_1_0;
75 else
76 return false;
77
78 return true;
79}
80
81/* Process HTTP headers. */
82static bool
83http_process_headers(struct http_transaction *ta)
84{
85 for (;;)
86 {
87 size_t header_offset;
88 ssize_t len = bufio_readline(ta->client->bufio, &header_offset);
89 if (len <= 0)
90 return false;
91
92 char *header = bufio_offset2ptr(ta->client->bufio, header_offset);
93 if (len == 2 && STARTS_WITH(header, CRLF)) // empty CRLF
94 return true;
95
96 header[len - 2] = '\0';
97 /* Each header field consists of a name followed by a
98 * colon (":") and the field value. Field names are
99 * case-insensitive. The field value MAY be preceded by
100 * any amount of LWS, though a single SP is preferred.
101 */
102 char *endptr;
103 char *field_name = strtok_r(header, ":", &endptr);
104 char *field_value = strtok_r(NULL, " \t", &endptr); // skip leading & trailing OWS
105
106 if (field_name == NULL)
107 return false;
108
109 //printf("Header: %s: %s\n", field_name, field_value);
110 if (!strcasecmp(field_name, "Content-Length"))
111 {
112 ta->req_content_len = atoi(field_value);
113 }
114
115 /* Handle other headers here. */
116 if (!strcasecmp(field_name, "Cookie"))
117 {
118 char *token = strstr(field_name, "auth_token=");
119 token += 11;
120 char *cookie = field_value + 11;
121 fprintf(stderr, "hey: %s\n", cookie);
122 if (jwt_decode(&ta->cookie, cookie,
123 (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE)))
124 perror("jwt_decode"),
125 exit(-1);
126 }
127 }
128}
129
130const int MAX_HEADER_LEN = 2048;
131
132/* add a formatted header to the response buffer. */
133void http_add_header(buffer_t *resp, char *key, char *fmt, ...)
134{
135 va_list ap;
136
137 buffer_appends(resp, key);
138 buffer_appends(resp, ": ");
139
140 va_start(ap, fmt);
141 char *error = buffer_ensure_capacity(resp, MAX_HEADER_LEN);
142 int len = vsnprintf(error, MAX_HEADER_LEN, fmt, ap);
143 resp->len += len > MAX_HEADER_LEN ? MAX_HEADER_LEN - 1 : len;
144 va_end(ap);
145
146 buffer_appends(resp, "\r\n");
147}
148
149/* add a content-length header. */
150static void
151add_content_length(buffer_t *res, size_t len)
152{
153 http_add_header(res, "Content-Length", "%ld", len);
154}
155
156/* start the response by writing the first line of the response
157 * to the response buffer. Used in send_response_header */
158static void
159start_response(struct http_transaction *ta, buffer_t *res)
160{
161 buffer_appends(res, "HTTP/1.0 ");
162
163 switch (ta->resp_status)
164 {
165 case HTTP_OK:
166 buffer_appends(res, "200 OK");
167 break;
168 case HTTP_BAD_REQUEST:
169 buffer_appends(res, "400 Bad Request");
170 break;
171 case HTTP_PERMISSION_DENIED:
172 buffer_appends(res, "403 Permission Denied");
173 break;
174 case HTTP_NOT_FOUND:
175 buffer_appends(res, "404 Not Found");
176 break;
177 case HTTP_METHOD_NOT_ALLOWED:
178 buffer_appends(res, "405 Method Not Allowed");
179 break;
180 case HTTP_REQUEST_TIMEOUT:
181 buffer_appends(res, "408 Request Timeout");
182 break;
183 case HTTP_REQUEST_TOO_LONG:
184 buffer_appends(res, "414 Request Too Long");
185 break;
186 case HTTP_NOT_IMPLEMENTED:
187 buffer_appends(res, "501 Not Implemented");
188 break;
189 case HTTP_SERVICE_UNAVAILABLE:
190 buffer_appends(res, "503 Service Unavailable");
191 break;
192 case HTTP_INTERNAL_ERROR:
193 default:
194 buffer_appends(res, "500 Internal Server Error");
195 break;
196 }
197 buffer_appends(res, CRLF);
198}
199
200/* Send response headers to client */
201static bool
202send_response_header(struct http_transaction *ta)
203{
204 buffer_t response;
205 buffer_init(&response, 80);
206
207 start_response(ta, &response);
208 if (bufio_sendbuffer(ta->client->bufio, &response) == -1)
209 return false;
210
211 buffer_appends(&ta->resp_headers, CRLF);
212 if (bufio_sendbuffer(ta->client->bufio, &ta->resp_headers) == -1)
213 return false;
214
215 buffer_delete(&response);
216 return true;
217}
218
219/* Send a full response to client with the content in resp_body. */
220static bool
221send_response(struct http_transaction *ta)
222{
223 // add content-length. All other headers must have already been set.
224 add_content_length(&ta->resp_headers, ta->resp_body.len);
225
226 if (!send_response_header(ta))
227 return false;
228
229 return bufio_sendbuffer(ta->client->bufio, &ta->resp_body) != -1;
230}
231
232const int MAX_ERROR_LEN = 2048;
233
234/* Send an error response. */
235static bool
236send_error(struct http_transaction *ta, enum http_response_status status, const char *fmt, ...)
237{
238 va_list ap;
239
240 va_start(ap, fmt);
241 char *error = buffer_ensure_capacity(&ta->resp_body, MAX_ERROR_LEN);
242 int len = vsnprintf(error, MAX_ERROR_LEN, fmt, ap);
243 ta->resp_body.len += len > MAX_ERROR_LEN ? MAX_ERROR_LEN - 1 : len;
244 va_end(ap);
245 ta->resp_status = status;
246 return send_response(ta);
247}
248
249/* Send Not Found response. */
250static bool
251send_not_found(struct http_transaction *ta)
252{
253 return send_error(ta, HTTP_NOT_FOUND, "File %s not found",
254 bufio_offset2ptr(ta->client->bufio, ta->req_path));
255}
256
257/* Send Forbidden response. */
258static bool
259send_forbidden(struct http_transaction *ta)
260{
261 return send_error(ta, HTTP_PERMISSION_DENIED, "Username and password do not match");
262}
263
264/* A start at assigning an appropriate mime type. Real-world
265 * servers use more extensive lists such as /etc/mime.types
266 */
267static const char *
268guess_mime_type(char *filename)
269{
270 char *suffix = strrchr(filename, '.');
271 if (suffix == NULL)
272 return "text/plain";
273
274 if (!strcasecmp(suffix, ".html"))
275 return "text/html";
276
277 if (!strcasecmp(suffix, ".gif"))
278 return "image/gif";
279
280 if (!strcasecmp(suffix, ".png"))
281 return "image/png";
282
283 if (!strcasecmp(suffix, ".jpg"))
284 return "image/jpeg";
285
286 if (!strcasecmp(suffix, ".js"))
287 return "text/javascript";
288
289 return "text/plain";
290}
291
292/* Handle HTTP transaction for static files. */
293static bool
294handle_static_asset(struct http_transaction *ta, char *basedir)
295{
296 char fname[PATH_MAX];
297
298 char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path);
299 // The code below is vulnerable to an attack. Can you see
300 // which? Fix it to avoid indirect object reference (IDOR) attacks.
301 snprintf(fname, sizeof fname, "%s%s", basedir, req_path);
302
303 char not_allowed[] = "..";
304 if (strstr(fname, not_allowed))
305 {
306 return send_error(ta, HTTP_NOT_FOUND, "Permission denied.");
307 }
308
309 if (access(fname, R_OK))
310 {
311 if (errno == EACCES)
312 return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied.");
313 else
314 return send_not_found(ta);
315 }
316
317 // Determine file size
318 struct stat st;
319 int rc = stat(fname, &st);
320 if (rc == -1)
321 return send_error(ta, HTTP_INTERNAL_ERROR, "Could not stat file.");
322
323 int filefd = open(fname, O_RDONLY);
324 if (filefd == -1)
325 {
326 return send_not_found(ta);
327 }
328
329 ta->resp_status = HTTP_OK;
330 add_content_length(&ta->resp_headers, st.st_size);
331 http_add_header(&ta->resp_headers, "Content-Type", "%s", guess_mime_type(fname));
332
333 bool success = send_response_header(ta);
334 if (!success)
335 goto out;
336
337 success = bufio_sendfile(ta->client->bufio, filefd, NULL, st.st_size) == st.st_size;
338out:
339 close(filefd);
340 return success;
341}
342
343static bool token_validated(struct http_transaction *ta)
344{
345 //Validatate the token
346 char *token = strstr(ta->cookie_offset, "auth_token=");
347 token += 11;
348 jwt_t *ymtoken;
349 if (jwt_decode(&ymtoken, token,
350 (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE)))
351 perror("jwt_decode"), exit(-1);
352
353 char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all
354 if (grants == NULL)
355 perror("jwt_get_grants_json"), exit(-1);
356
357 //char *userBuff = strstr(grants, "sub");
358 char const *sub = jwt_get_grant(ymtoken, "sub");
359 long iat = jwt_get_grant_int(ymtoken, "iat");
360 long exp = jwt_get_grant_int(ymtoken, "exp");
361
362 time_t now = time(NULL);
363 fprintf(stderr, "%s : %ld : %ld\n", sub, iat, exp);
364 fprintf(stderr, "%s\n", grants);
365
366 if (sub && iat && exp && (now < exp))
367 {
368 return true;
369 }
370
371 return false;
372}
373
374static int
375handle_api(struct http_transaction *ta)
376{
377 //fprintf(stderr, "The GET is: %s\n", bufio_offset2ptr(ta->client->bufio, ta->req_body));
378 if (ta->req_method == HTTP_GET) /* */
379 {
380 // Check if user has a cookie for server to validate
381 char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path);
382 if (!strcmp(req_path, "/api/login"))
383 {
384 if (!ta->cookie_offset)
385 {
386 ta->resp_status = HTTP_OK;
387 buffer_appends(&ta->resp_body, "{}");
388 }
389 else
390 {
391 if (token_validated(ta))
392 {
393 char *token = strstr(ta->cookie_offset, "auth_token=");
394 token += 12;
395 jwt_t *ymtoken;
396 if (jwt_decode(&ymtoken, token,
397 (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE)))
398 perror("jwt_decode"), exit(-1);
399
400 char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all
401 if (grants == NULL)
402 perror("jwt_get_grants_json"), exit(-1);
403
404 buffer_appends(&ta->resp_body, grants);
405 ta->resp_status = HTTP_OK;
406 }
407 else
408 {
409 ta->resp_status = HTTP_OK;
410 buffer_appends(&ta->resp_body, "{}");
411 }
412 }
413
414 return send_response(ta);
415 }
416 else
417 {
418 return send_error(ta, HTTP_NOT_FOUND, "API not implemented");
419 }
420 }
421 else if (ta->req_method == HTTP_POST) /* */
422 {
423 char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path);
424 if (!strcmp(req_path, "/api/login"))
425 {
426 char *buffer = bufio_offset2ptr(ta->client->bufio, ta->req_body);
427 json_t *buff = json_loadb(buffer, strlen(buffer), 0, NULL);
428
429 char *username = NULL;
430 char *password = NULL;
431 json_unpack(buff, "{s:s, s:s}", "username", &username, "password", &password);
432
433 fprintf(stderr, "the user is: %s and the password is: %s\n", username, password);
434
435 if (!strcmp(username, "user0") && !strcmp(password, "thepassword"))
436 {
437 jwt_t *mytoken;
438
439 if (jwt_new(&mytoken))
440 perror("jwt_new"), exit(-1);
441
442 if (jwt_add_grant(mytoken, "sub", "user0"))
443 perror("jwt_add_grant sub"), exit(-1);
444
445 time_t now = time(NULL);
446 if (jwt_add_grant_int(mytoken, "iat", now))
447 perror("jwt_add_grant iat"), exit(-1);
448
449 if (jwt_add_grant_int(mytoken, "exp", now + token_expiration_time))
450 perror("jwt_add_grant exp"), exit(-1);
451
452 if (jwt_set_alg(mytoken, JWT_ALG_HS256,
453 (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE)))
454 perror("jwt_set_alg"), exit(-1);
455
456 char *encoded = jwt_encode_str(mytoken);
457 if (encoded == NULL)
458 perror("jwt_encode_str"), exit(-1);
459
460 char *grants = jwt_get_grants_json(mytoken, NULL); // NULL means all
461 if (grants == NULL)
462 perror("jwt_get_grants_json"), exit(-1);
463
464 //printf("redecoded: %s\n", grants);
465 //printf("encoded as %s\nTry entering this at jwt.io\n", encoded);
466 //fprintf(stderr, "The expire time is: %d\n", token_expiration_time);
467 //fprintf(stderr, "THe response is: %s\n", ta->resp_headers.buf);
468
469 /* Set up the response to send back to client */
470 /* Set cookie */
471 http_add_header(&ta->resp_headers, "Set-Cookie", "auth_token=%s", encoded);
472 buffer_appends(&ta->resp_body, grants);
473 ta->resp_status = HTTP_OK;
474 //fprintf(stderr, "THe response is: %s\n", ta->resp_headers.buf);
475 send_response(ta);
476 }
477 else
478 {
479 return send_forbidden(ta);
480 }
481 }
482 else
483 {
484 return send_error(ta, HTTP_NOT_FOUND, "API not implemented");
485 }
486 }
487 else
488 {
489 return send_error(ta, HTTP_METHOD_NOT_ALLOWED, "API not implemented");
490 }
491 return 0;
492}
493
494static int
495handle_private(struct http_transaction *ta)
496{
497 if (ta->cookie_offset != 0)
498 {
499 if (token_validated(ta))
500 {
501 ta->resp_status = HTTP_OK;
502 return handle_static_asset(ta, server_root);
503 }
504 else
505 {
506 return send_error(ta, HTTP_PERMISSION_DENIED, "Authentication failed");
507 }
508 }
509 else
510 {
511 return send_error(ta, HTTP_PERMISSION_DENIED, "Permission was denied");
512 }
513 return 0;
514}
515
516/* Set up an http client, associating it with a bufio buffer. */
517void http_setup_client(struct http_client *self, struct bufio *bufio)
518{
519 self->bufio = bufio;
520}
521
522/* Handle a single HTTP transaction. Returns true on success. */
523bool http_handle_transaction(struct http_client *self)
524{
525 struct http_transaction ta;
526 memset(&ta, 0, sizeof ta);
527 ta.client = self;
528
529 if (!http_parse_request(&ta))
530 return false;
531
532 if (!http_process_headers(&ta))
533 return false;
534
535 if (ta.req_content_len > 0)
536 {
537 int rc = bufio_read(self->bufio, ta.req_content_len, &ta.req_body);
538 if (rc != ta.req_content_len)
539 return false;
540
541 // To see the body, use this:ha
542 //char *body = bufio_offset2ptr(ta.client->bufio, ta.req_body);
543 //hexdump(body, ta.req_content_len);
544 }
545
546 buffer_init(&ta.resp_headers, 1024);
547 http_add_header(&ta.resp_headers, "Server", "CS3214-Personal-Server");
548 buffer_init(&ta.resp_body, 0);
549
550 bool rc = false;
551 char *req_path = bufio_offset2ptr(ta.client->bufio, ta.req_path);
552 if (STARTS_WITH(req_path, "/api"))
553 {
554 rc = handle_api(&ta);
555 }
556 else if (STARTS_WITH(req_path, "/private"))
557 {
558 rc = handle_private(&ta);
559 }
560 else
561 {
562 rc = handle_static_asset(&ta, server_root);
563 }
564
565 buffer_delete(&ta.resp_headers);
566 buffer_delete(&ta.resp_body);
567
568 return rc;
569}