1 /*
2 * Copyright (C) Tildeslash Ltd. All rights reserved.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU Affero General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *
15 * In addition, as a special exception, the copyright holders give
16 * permission to link the code of portions of this program with the
17 * OpenSSL library under certain conditions as described in each
18 * individual source file, and distribute linked combinations
19 * including the two.
20 *
21 * You must obey the GNU Affero General Public License in all respects
22 * for all of the code used other than OpenSSL.
23 */
24
25 #include "config.h"
26
27 #ifdef HAVE_STDIO_H
28 #include <stdio.h>
29 #endif
30
31 #ifdef HAVE_STDLIB_H
32 #include <stdlib.h>
33 #endif
34
35 #ifdef HAVE_ERRNO_H
36 #include <errno.h>
37 #endif
38
39 #ifdef HAVE_STDARG_H
40 #include <stdarg.h>
41 #endif
42
43 #ifdef HAVE_SYS_TYPES_H
44 #include <sys/types.h>
45 #endif
46
47 #ifdef HAVE_SYS_SOCKET_H
48 #include <sys/socket.h>
49 #endif
50
51 #ifdef HAVE_SETJMP_H
52 #include <setjmp.h>
53 #endif
54
55 #ifdef HAVE_STRING_H
56 #include <string.h>
57 #endif
58
59 #ifdef HAVE_STRINGS_H
60 #include <strings.h>
61 #endif
62
63 #ifdef HAVE_UNISTD_H
64 #include <unistd.h>
65 #endif
66
67 #ifdef HAVE_LIMITS_H
68 #include <limits.h>
69 #endif
70
71 #include "monit.h"
72 #include "processor.h"
73 #include "base64.h"
74
75 // libmonit
76 #include "util/Str.h"
77 #include "system/Net.h"
78
79
80 /**
81 * A naive quasi HTTP Processor module that can handle HTTP requests
82 * received from a client, and return responses based on those
83 * requests.
84 *
85 * This Processor delegates the actual handling of the request and
86 * response to so called cervlets, which must implement two methods;
87 * doGet and doPost.
88 *
89 * NOTES
90 * This Processor is command oriented and if a second slash '/' is
91 * found in the URL it's assumed to be the PATHINFO. In other words
92 * this processor perceive an URL as:
93 *
94 * /COMMAND?QUERYSTRING/PATHINFO
95 *
96 * The doGet/doPost routines act's on the COMMAND. See the
97 * cervlet.c code in this dir. for an example.
98 *
99 * @file
100 */
101
102
103 static int _httpPostLimit;
104
105
106 /* -------------------------------------------------------------- Prototypes */
107
108
109 static void do_service(Socket_T);
110 static void destroy_entry(void *);
111 static char *get_date(char *, int);
112 static char *get_server(char *, int);
113 static void create_headers(HttpRequest);
114 static void send_response(HttpRequest, HttpResponse);
115 static bool basic_authenticate(HttpRequest);
116 static void done(HttpRequest, HttpResponse);
117 static void destroy_HttpRequest(HttpRequest);
118 static void reset_response(HttpResponse res);
119 static HttpParameter parse_parameters(char *);
120 static bool create_parameters(HttpRequest req);
121 static void destroy_HttpResponse(HttpResponse);
122 static HttpRequest create_HttpRequest(Socket_T);
123 static void internal_error(Socket_T, int, const char *);
124 static HttpResponse create_HttpResponse(Socket_T);
125 static bool is_authenticated(HttpRequest, HttpResponse);
126 static int get_next_token(char *s, int *cursor, char **r);
127
128
129 /*
130 * An object for implementors of the service functions; doGet and
131 * doPost. Implementing modules i.e. CERVLETS, must implement the
132 * doGet and doPost functions and the engine will call the add_Impl
133 * function to setup the callback to these functions.
134 */
135 struct ServiceImpl {
136 void(*doGet)(HttpRequest, HttpResponse);
137 void(*doPost)(HttpRequest, HttpResponse);
138 } Impl;
139
140
141 /* ------------------------------------------------------------------ Public */
142
143
144 /**
145 * Process a HTTP request. This is done by dispatching to the service
146 * function.
147 * @param s A Socket_T representing the client connection
148 */
http_processor(Socket_T s)149 void *http_processor(Socket_T s) {
150 if (! Net_canRead(Socket_getSocket(s), REQUEST_TIMEOUT * 1000))
151 internal_error(s, SC_REQUEST_TIMEOUT, "Time out when handling the Request");
152 else
153 do_service(s);
154 Socket_free(&s);
155 return NULL;
156 }
157
158
159 /**
160 * Callback for implementors of cervlet functions.
161 * @param doGetFunc doGet function
162 * @param doPostFunc doPost function
163 */
add_Impl(void (* doGet)(HttpRequest,HttpResponse),void (* doPost)(HttpRequest,HttpResponse))164 void add_Impl(void(*doGet)(HttpRequest, HttpResponse), void(*doPost)(HttpRequest, HttpResponse)) {
165 Impl.doGet = doGet;
166 Impl.doPost = doPost;
167 }
168
169
Processor_setHttpPostLimit()170 void Processor_setHttpPostLimit() {
171 // Base buffer size (space for e.g. "action=<name>")
172 _httpPostLimit = STRLEN;
173 // Add space for each service
174 for (Service_T s = servicelist; s; s = s->next)
175 _httpPostLimit += strlen("&service=") + strlen(s->name);
176 }
177
178
escapeHTML(StringBuffer_T sb,const char * s)179 StringBuffer_T escapeHTML(StringBuffer_T sb, const char *s) {
180 for (int i = 0; s[i]; i++) {
181 if (s[i] == '<')
182 StringBuffer_append(sb, "<");
183 else if (s[i] == '>')
184 StringBuffer_append(sb, ">");
185 else if (s[i] == '&')
186 StringBuffer_append(sb, "&");
187 else
188 StringBuffer_append(sb, "%c", s[i]);
189 }
190 return sb;
191 }
192
193
194 /**
195 * Send an error message
196 * @param res HttpResponse object
197 * @param code Error Code to lookup and send
198 * @param msg Optional error message (may be NULL)
199 */
send_error(HttpRequest req,HttpResponse res,int code,const char * msg,...)200 void send_error(HttpRequest req, HttpResponse res, int code, const char *msg, ...) {
201 ASSERT(msg);
202
203 const char *err = get_status_string(code);
204 reset_response(res);
205 set_content_type(res, "text/html");
206 set_status(res, code);
207 StringBuffer_append(res->outputbuffer,
208 "<html>"
209 "<head>"
210 "<title>%d %s</title>"
211 "</head>"
212 "<body bgcolor=#FFFFFF>"
213 "<h2>%s</h2>",
214 code, err, err);
215 char *message;
216 va_list ap;
217 va_start(ap, msg);
218 message = Str_vcat(msg, ap);
219 va_end(ap);
220 escapeHTML(res->outputbuffer, message);
221 if (code != SC_UNAUTHORIZED) // We log details in basic_authenticate() already, no need to log generic error sent to client here
222 Log_error("HttpRequest: error -- client [%s]: %s %d %s\n", NVLSTR(Socket_getRemoteHost(req->S)), SERVER_PROTOCOL, code, message);
223 FREE(message);
224 char server[STRLEN];
225 StringBuffer_append(res->outputbuffer,
226 "<hr>"
227 "<a href='%s'><font size=-1>%s</font></a>"
228 "</body>"
229 "</html>"
230 "\r\n",
231 SERVER_URL, get_server(server, STRLEN));
232 }
233
234
235 /* -------------------------------------------------------------- Properties */
236
237
238 /**
239 * Adds a response header with the given name and value. If the header
240 * had already been set the new value overwrites the previous one.
241 * @param res HttpResponse object
242 * @param name Header key name
243 * @param value Header key value
244 */
set_header(HttpResponse res,const char * name,const char * value,...)245 void set_header(HttpResponse res, const char *name, const char *value, ...) {
246 HttpHeader h = NULL;
247
248 ASSERT(res);
249 ASSERT(name);
250
251 NEW(h);
252 h->name = Str_dup(name);
253 va_list ap;
254 va_start(ap, value);
255 h->value = Str_vcat(value, ap);
256 va_end(ap);
257 if (res->headers) {
258 HttpHeader n, p;
259 for (n = p = res->headers; p; n = p, p = p->next) {
260 if (IS(p->name, name)) {
261 FREE(p->value);
262 p->value = Str_dup(h->value);
263 destroy_entry(h);
264 return;
265 }
266 }
267 n->next = h;
268 } else {
269 res->headers = h;
270 }
271 }
272
273
274 /**
275 * Sets the status code for the response
276 * @param res HttpResponse object
277 * @param code A HTTP status code <100-510>
278 * @param msg The status code string message
279 */
set_status(HttpResponse res,int code)280 void set_status(HttpResponse res, int code) {
281 res->status = code;
282 res->status_msg = get_status_string(code);
283 }
284
285
286 /**
287 * Set the response content-type
288 * @param res HttpResponse object
289 * @param mime Mime content type, e.g. text/html
290 */
set_content_type(HttpResponse res,const char * mime)291 void set_content_type(HttpResponse res, const char *mime) {
292 ASSERT(mime);
293 set_header(res, "Content-Type", "%s", mime);
294 }
295
296
297 /**
298 * Returns the value of the specified header
299 * @param req HttpRequest object
300 * @param name Header name to lookup the value for
301 * @return The value of the specified header, NULL if not found
302 */
get_header(HttpRequest req,const char * name)303 const char *get_header(HttpRequest req, const char *name) {
304 for (HttpHeader p = req->headers; p; p = p->next)
305 if (IS(p->name, name))
306 return (p->value);
307 return NULL;
308 }
309
310
311 /**
312 * Returns the value of the specified parameter
313 * @param req HttpRequest object
314 * @param name The request parameter key to lookup the value for
315 * @return The value of the specified parameter, or NULL if not found
316 */
get_parameter(HttpRequest req,const char * name)317 const char *get_parameter(HttpRequest req, const char *name) {
318 for (HttpParameter p = req->params; p; p = p->next)
319 if (IS(p->name, name))
320 return (p->value);
321 return NULL;
322 }
323
324
325 /**
326 * Returns a string containing all (extra) headers found in the
327 * response. The headers are newline separated in the returned
328 * string.
329 * @param res HttpResponse object
330 * @return A String containing all headers set in the Response object
331 */
get_headers(HttpResponse res)332 char *get_headers(HttpResponse res) {
333 char buf[RES_STRLEN];
334 char *b = buf;
335 *buf = 0;
336 for (HttpHeader p = res->headers; (((b - buf) + STRLEN) < RES_STRLEN) && p; p = p->next)
337 b += snprintf(b, STRLEN,"%s: %s\r\n", p->name, p->value);
338 return buf[0] ? Str_dup(buf) : NULL;
339 }
340
341
342 /**
343 * Lookup the corresponding HTTP status string for the given status
344 * code
345 * @param status A HTTP status code
346 * @return A default status message for the specified HTTP status
347 * code.
348 */
get_status_string(int status)349 const char *get_status_string(int status) {
350 switch (status) {
351 case SC_OK:
352 return "OK";
353 case SC_ACCEPTED:
354 return "Accepted";
355 case SC_BAD_GATEWAY:
356 return "Bad Gateway";
357 case SC_BAD_REQUEST:
358 return "Bad Request";
359 case SC_CONFLICT:
360 return "Conflict";
361 case SC_CONTINUE:
362 return "Continue";
363 case SC_CREATED:
364 return "Created";
365 case SC_EXPECTATION_FAILED:
366 return "Expectation Failed";
367 case SC_FORBIDDEN:
368 return "Forbidden";
369 case SC_GATEWAY_TIMEOUT:
370 return "Gateway Timeout";
371 case SC_GONE:
372 return "Gone";
373 case SC_VERSION_NOT_SUPPORTED:
374 return "HTTP Version Not Supported";
375 case SC_INTERNAL_SERVER_ERROR:
376 return "Internal Server Error";
377 case SC_LENGTH_REQUIRED:
378 return "Length Required";
379 case SC_METHOD_NOT_ALLOWED:
380 return "Method Not Allowed";
381 case SC_MOVED_PERMANENTLY:
382 return "Moved Permanently";
383 case SC_MOVED_TEMPORARILY:
384 return "Moved Temporarily";
385 case SC_MULTIPLE_CHOICES:
386 return "Multiple Choices";
387 case SC_NO_CONTENT:
388 return "No Content";
389 case SC_NON_AUTHORITATIVE:
390 return "Non-Authoritative Information";
391 case SC_NOT_ACCEPTABLE:
392 return "Not Acceptable";
393 case SC_NOT_FOUND:
394 return "Not Found";
395 case SC_NOT_IMPLEMENTED:
396 return "Not Implemented";
397 case SC_NOT_MODIFIED:
398 return "Not Modified";
399 case SC_PARTIAL_CONTENT:
400 return "Partial Content";
401 case SC_PAYMENT_REQUIRED:
402 return "Payment Required";
403 case SC_PRECONDITION_FAILED:
404 return "Precondition Failed";
405 case SC_PROXY_AUTHENTICATION_REQUIRED:
406 return "Proxy Authentication Required";
407 case SC_REQUEST_ENTITY_TOO_LARGE:
408 return "Request Entity Too Large";
409 case SC_REQUEST_TIMEOUT:
410 return "Request Timeout";
411 case SC_REQUEST_URI_TOO_LARGE:
412 return "Request URI Too Large";
413 case SC_RANGE_NOT_SATISFIABLE:
414 return "Requested Range Not Satisfiable";
415 case SC_RESET_CONTENT:
416 return "Reset Content";
417 case SC_SEE_OTHER:
418 return "See Other";
419 case SC_SERVICE_UNAVAILABLE:
420 return "Service Unavailable";
421 case SC_SWITCHING_PROTOCOLS:
422 return "Switching Protocols";
423 case SC_UNAUTHORIZED:
424 return "Unauthorized";
425 case SC_UNSUPPORTED_MEDIA_TYPE:
426 return "Unsupported Media Type";
427 case SC_USE_PROXY:
428 return "Use Proxy";
429 default: {
430 return "Unknown HTTP status";
431 }
432 }
433 }
434
435
436 /* ----------------------------------------------------------------- Private */
437
438
439 /**
440 * Receives standard HTTP requests from a client socket and dispatches
441 * them to the doXXX methods defined in a cervlet module.
442 */
do_service(Socket_T s)443 static void do_service(Socket_T s) {
444 volatile HttpResponse res = create_HttpResponse(s);
445 volatile HttpRequest req = create_HttpRequest(s);
446 if (res && req) {
447 if (Run.httpd.socket.net.ssl.flags & SSL_Enabled)
448 set_header(res, "Strict-Transport-Security", "max-age=63072000; includeSubdomains; preload");
449 if (is_authenticated(req, res)) {
450 set_header(res, "Set-Cookie", "securitytoken=%s; Max-Age=600; HttpOnly; SameSite=strict%s", res->token, (Run.httpd.socket.net.ssl.flags & SSL_Enabled) ? "; Secure" : "");
451 if (IS(req->method, METHOD_GET))
452 Impl.doGet(req, res);
453 else if (IS(req->method, METHOD_POST))
454 Impl.doPost(req, res);
455 else
456 send_error(req, res, SC_NOT_IMPLEMENTED, "Method not implemented");
457 }
458 send_response(req, res);
459 }
460 done(req, res);
461 }
462
463
464 /**
465 * Return a (RFC1123) Date string
466 */
get_date(char * result,int size)467 static char *get_date(char *result, int size) {
468 time_t now;
469 struct tm converted;
470 time(&now);
471 if (strftime(result, size, DATEFMT, gmtime_r(&now, &converted)) <= 0)
472 *result = 0;
473 return result;
474 }
475
476
477 /**
478 * Return this server name + version
479 */
get_server(char * result,int size)480 static char *get_server(char *result, int size) {
481 snprintf(result, size, "%s %s", SERVER_NAME, Run.httpd.flags & Httpd_Signature ? SERVER_VERSION : "");
482 return result;
483 }
484
485
486 /**
487 * Send the response to the client. If the response has already been
488 * committed, this function does nothing.
489 */
send_response(HttpRequest req,HttpResponse res)490 static void send_response(HttpRequest req, HttpResponse res) {
491 Socket_T S = res->S;
492
493 if (! res->is_committed) {
494 char date[STRLEN];
495 char server[STRLEN];
496 #ifdef HAVE_LIBZ
497 const char *acceptEncoding = get_header(req, "Accept-Encoding");
498 bool canCompress = acceptEncoding && Str_sub(acceptEncoding, "gzip");
499 #else
500 bool canCompress = false;
501 #endif
502 const void *body = NULL;
503 size_t bodyLength = 0;
504 if (canCompress && StringBuffer_length(res->outputbuffer) > 0) {
505 body = StringBuffer_toCompressed(res->outputbuffer, 6, &bodyLength);
506 set_header(res, "Content-Encoding", "gzip");
507 } else {
508 body = StringBuffer_toString(res->outputbuffer);
509 bodyLength = StringBuffer_length(res->outputbuffer);
510 }
511 char *headers = get_headers(res);
512 res->is_committed = true;
513 get_date(date, STRLEN);
514 get_server(server, STRLEN);
515 Socket_print(S, "%s %d %s\r\n", res->protocol, res->status, res->status_msg);
516 Socket_print(S, "Date: %s\r\n", date);
517 Socket_print(S, "Server: %s\r\n", server);
518 Socket_print(S, "Content-Length: %zu\r\n", bodyLength);
519 Socket_print(S, "Connection: close\r\n");
520 if (headers)
521 Socket_print(S, "%s", headers);
522 Socket_print(S, "\r\n");
523 if (bodyLength)
524 if(Socket_write(S, body, bodyLength) < 0)
525 Log_error("Http: Cannot send the response -- %s\n", STRERROR);
526 FREE(headers);
527 }
528 }
529
530
531 /* --------------------------------------------------------------- Factories */
532
533
534 /**
535 * Returns a new HttpRequest object wrapping the client request
536 */
create_HttpRequest(Socket_T S)537 static HttpRequest create_HttpRequest(Socket_T S) {
538 char line[REQ_STRLEN];
539 if (Socket_readLine(S, line, sizeof(line)) == NULL) {
540 internal_error(S, SC_BAD_REQUEST, "No request found");
541 return NULL;
542 }
543 Str_chomp(line);
544 char method[STRLEN];
545 char url[REQ_STRLEN];
546 char protocol[STRLEN];
547 if (sscanf(line, "%255s %1023s HTTP/%3[1.0]", method, url, protocol) != 3) {
548 internal_error(S, SC_BAD_REQUEST, "Cannot parse request");
549 return NULL;
550 }
551 if (strlen(url) >= MAX_URL_LENGTH) {
552 internal_error(S, SC_BAD_REQUEST, "[error] URL too long");
553 return NULL;
554 }
555 HttpRequest req = NULL;
556 NEW(req);
557 req->S = S;
558 Util_urlDecode(url);
559 req->url = Str_dup(url);
560 req->method = Str_dup(method);
561 req->protocol = Str_dup(protocol);
562 create_headers(req);
563 if (! create_parameters(req)) {
564 destroy_HttpRequest(req);
565 internal_error(S, SC_BAD_REQUEST, "Cannot parse Request parameters");
566 return NULL;
567 }
568 return req;
569 }
570
571
572 /**
573 * Returns a new HttpResponse object wrapping a default response. Use
574 * the set_XXX methods to change the object.
575 */
create_HttpResponse(Socket_T S)576 static HttpResponse create_HttpResponse(Socket_T S) {
577 HttpResponse res = NULL;
578 NEW(res);
579 res->S = S;
580 res->status = SC_OK;
581 res->outputbuffer = StringBuffer_create(256);
582 res->is_committed = false;
583 res->protocol = SERVER_PROTOCOL;
584 res->status_msg = get_status_string(SC_OK);
585 Util_getToken(res->token);
586 return res;
587 }
588
589
590 /**
591 * Create HTTP headers for the given request
592 */
create_headers(HttpRequest req)593 static void create_headers(HttpRequest req) {
594 char line[REQ_STRLEN] = {0};
595 while (Socket_readLine(req->S, line, sizeof(line)) && ! (Str_isEqual(line, "\r\n") || Str_isEqual(line, "\n"))) {
596 char *value = strchr(line, ':');
597 if (value) {
598 HttpHeader header = NULL;
599 NEW(header);
600 *value++ = 0;
601 Str_trim(line);
602 Str_trim(value);
603 Str_chomp(value);
604 header->name = Str_dup(line);
605 header->value = Str_dup(value);
606 header->next = req->headers;
607 req->headers = header;
608 }
609 }
610 }
611
612
613 /**
614 * Create parameters for the given request. Returns false if an error
615 * occurs.
616 */
create_parameters(HttpRequest req)617 static bool create_parameters(HttpRequest req) {
618 char *query_string = NULL;
619 if (IS(req->method, METHOD_POST)) {
620 int len;
621 const char *content_length = get_header(req, "Content-Length");
622 if (! content_length || sscanf(content_length, "%d", &len) != 1 || len < 0 || len > _httpPostLimit)
623 return false;
624 if (len != 0) {
625 query_string = CALLOC(1, _httpPostLimit + 1);
626 int n = Socket_read(req->S, query_string, len);
627 if (n != len) {
628 FREE(query_string);
629 return false;
630 }
631 }
632 } else if (IS(req->method, METHOD_GET)) {
633 char *p = strchr(req->url, '?');
634 if (p) {
635 *p++ = 0;
636 query_string = Str_dup(p);
637 }
638 }
639 if (query_string) {
640 if (*query_string) {
641 char *p = strchr(query_string, '/');
642 if (p) {
643 *p++ = 0;
644 req->pathinfo = Str_dup(p);
645 }
646 req->params = parse_parameters(query_string);
647 }
648 FREE(query_string);
649 }
650 return true;
651 }
652
653
654 /* ----------------------------------------------------------------- Cleanup */
655
656
657 /**
658 * Clear the response output buffer and headers
659 */
reset_response(HttpResponse res)660 static void reset_response(HttpResponse res) {
661 if (res->headers) {
662 destroy_entry(res->headers);
663 res->headers = NULL; /* Release Pragma */
664 }
665 StringBuffer_clear(res->outputbuffer);
666 }
667
668
669 /**
670 * Finalize the request and response object.
671 */
done(HttpRequest req,HttpResponse res)672 static void done(HttpRequest req, HttpResponse res) {
673 destroy_HttpRequest(req);
674 destroy_HttpResponse(res);
675 }
676
677
678 /**
679 * Free a HttpRequest object
680 */
destroy_HttpRequest(HttpRequest req)681 static void destroy_HttpRequest(HttpRequest req) {
682 if (req) {
683 FREE(req->method);
684 FREE(req->url);
685 FREE(req->pathinfo);
686 FREE(req->protocol);
687 FREE(req->remote_user);
688 if (req->headers)
689 destroy_entry(req->headers);
690 if (req->params)
691 destroy_entry(req->params);
692 FREE(req);
693 }
694 }
695
696
697 /**
698 * Free a HttpResponse object
699 */
destroy_HttpResponse(HttpResponse res)700 static void destroy_HttpResponse(HttpResponse res) {
701 if (res) {
702 StringBuffer_free(&(res->outputbuffer));
703 if (res->headers)
704 destroy_entry(res->headers);
705 FREE(res);
706 }
707 }
708
709
710 /**
711 * Free a (linked list of) http entry object(s). Both HttpHeader and
712 * HttpParameter are of this type.
713 */
destroy_entry(void * p)714 static void destroy_entry(void *p) {
715 struct entry *h = p;
716 if (h->next)
717 destroy_entry(h->next);
718 FREE(h->name);
719 FREE(h->value);
720 FREE(h);
721 }
722
723
724 /* ----------------------------------------------------- Checkers/Validators */
725
726
_isCookieSeparator(int c)727 static bool _isCookieSeparator(int c) {
728 return (c == ' ' || c == '\n' || c == ';' || c == ',');
729 }
730
731
is_authenticated(HttpRequest req,HttpResponse res)732 static bool is_authenticated(HttpRequest req, HttpResponse res) {
733 if (Run.httpd.credentials) {
734 if (! basic_authenticate(req)) {
735 // Send just generic error message to the client to not disclose e.g. username existence in case of credentials harvesting attack
736 send_error(req, res, SC_UNAUTHORIZED, "You are not authorized to access monit. Either you supplied the wrong credentials (e.g. bad password), or your browser doesn't understand how to supply the credentials required");
737 set_header(res, "WWW-Authenticate", "Basic realm=\"monit\"");
738 return false;
739 }
740 }
741 if (IS(req->method, METHOD_POST)) {
742 // Check CSRF double-submit cookie (https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Double_Submit_Cookie)
743 const char *token = get_parameter(req, "securitytoken");
744 if (! token) {
745 Log_error("HttpRequest: access denied -- client [%s]: missing CSRF token in HTTP parameter\n", NVLSTR(Socket_getRemoteHost(req->S)));
746 send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
747 return false;
748 }
749 const char *cookie = get_header(req, "Cookie");
750 if (! cookie) {
751 Log_error("HttpRequest: access denied -- client [%s]: missing CSRF token cookie\n", NVLSTR(Socket_getRemoteHost(req->S)));
752 send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
753 return false;
754 }
755 const char *cookieName = "securitytoken=";
756 for (int i = 0, j = 0; cookie[i]; i++) {
757 if (_isCookieSeparator(cookie[i])) {
758 // Cookie separator
759 j = 0;
760 continue;
761 }
762 if (j < 14) {
763 // Cookie name
764 if (cookie[i] == cookieName[j]) {
765 j++;
766 continue;
767 } else {
768 j = 0;
769 }
770 } else if (j == 14) {
771 // Cookie value
772 char cookieValue[STRLEN] = {};
773 strncpy(cookieValue, cookie + i, sizeof(cookieValue) - 1);
774 for (int k = 0; cookieValue[k]; k++) {
775 if (_isCookieSeparator(cookieValue[k])) {
776 cookieValue[k] = 0;
777 break;
778 }
779 }
780 if (Str_compareConstantTime(cookieValue, token)) {
781 Log_error("HttpRequest: access denied -- client [%s]: CSRF token mismatch\n", NVLSTR(Socket_getRemoteHost(req->S)));
782 send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
783 return false;
784 }
785 return true;
786 }
787 }
788 Log_error("HttpRequest: access denied -- client [%s]: no CSRF token in cookie\n", NVLSTR(Socket_getRemoteHost(req->S)));
789 send_error(req, res, SC_FORBIDDEN, "Invalid CSRF Token");
790 return false;
791 }
792 return true;
793 }
794
795
796 /**
797 * Authenticate the basic-credentials (uname/password) submitted by
798 * the user.
799 */
basic_authenticate(HttpRequest req)800 static bool basic_authenticate(HttpRequest req) {
801 const char *credentials = get_header(req, "Authorization");
802 if (! (credentials && Str_startsWith(credentials, "Basic "))) {
803 Log_debug("HttpRequest: access denied -- client [%s]: missing or invalid Authorization header\n", NVLSTR(Socket_getRemoteHost(req->S)));
804 return false;
805 }
806 char buf[STRLEN] = {0};
807 strncpy(buf, &credentials[6], sizeof(buf) - 1);
808 char uname[STRLEN] = {0};
809 if (decode_base64((unsigned char *)uname, buf) <= 0) {
810 Log_debug("HttpRequest: access denied -- client [%s]: invalid Authorization header\n", NVLSTR(Socket_getRemoteHost(req->S)));
811 return false;
812 }
813 if (STR_UNDEF(uname)) {
814 Log_debug("HttpRequest: access denied -- client [%s]: empty username\n", NVLSTR(Socket_getRemoteHost(req->S)));
815 return false;
816 }
817 char *password = strchr(uname, ':');
818 if (STR_UNDEF(password)) {
819 Log_debug("HttpRequest: access denied -- client [%s]: empty password\n", NVLSTR(Socket_getRemoteHost(req->S)));
820 return false;
821 }
822 *password++ = 0;
823 /* Check if user exist */
824 if (! Util_getUserCredentials(uname)) {
825 Log_error("HttpRequest: access denied -- client [%s]: unknown user '%s'\n", NVLSTR(Socket_getRemoteHost(req->S)), uname);
826 return false;
827 }
828 /* Check if user has supplied the right password */
829 if (! Util_checkCredentials(uname, password)) {
830 Log_error("HttpRequest: access denied -- client [%s]: wrong password for user '%s'\n", NVLSTR(Socket_getRemoteHost(req->S)), uname);
831 return false;
832 }
833 req->remote_user = Str_dup(uname);
834 return true;
835 }
836
837
838 /* --------------------------------------------------------------- Utilities */
839
840
841 /**
842 * Send an error message to the client. This is a helper function,
843 * used internal if the service function fails to setup the framework
844 * properly; i.e. with a valid HttpRequest and a valid HttpResponse.
845 */
internal_error(Socket_T S,int status,const char * msg)846 static void internal_error(Socket_T S, int status, const char *msg) {
847 char date[STRLEN];
848 char server[STRLEN];
849 const char *status_msg = get_status_string(status);
850
851 get_date(date, STRLEN);
852 get_server(server, STRLEN);
853 Socket_print(S,
854 "%s %d %s\r\n"
855 "Date: %s\r\n"
856 "Server: %s\r\n"
857 "Content-Type: text/html\r\n"
858 "Connection: close\r\n"
859 "\r\n"
860 "<html><head><title>%s</title></head>"
861 "<body bgcolor=#FFFFFF><h2>%s</h2>%s<p>"
862 "<hr><a href='%s'><font size=-1>%s</font></a>"
863 "</body></html>\r\n",
864 SERVER_PROTOCOL, status, status_msg, date, server,
865 status_msg, status_msg, msg, SERVER_URL, server);
866 DEBUG("HttpRequest: error -- client [%s]: %s %d %s\n", NVLSTR(Socket_getRemoteHost(S)), SERVER_PROTOCOL, status, msg ? msg : status_msg);
867 }
868
869
870 /**
871 * Parse request parameters from the given query string and return a
872 * linked list of HttpParameters
873 */
parse_parameters(char * query_string)874 static HttpParameter parse_parameters(char *query_string) {
875 #define KEY 1
876 #define VALUE 2
877 int token;
878 int cursor = 0;
879 char *key = NULL;
880 char *value = NULL;
881 HttpParameter head = NULL;
882
883 while ((token = get_next_token(query_string, &cursor, &value))) {
884 if (token == KEY)
885 key = value;
886 else if (token == VALUE) {
887 HttpParameter p = NULL;
888 if (! key)
889 goto error;
890 NEW(p);
891 p->name = key;
892 p->value = Util_urlDecode(value);
893 p->next = head;
894 head = p;
895 key = NULL;
896 }
897 }
898 if (key)
899 FREE(key);
900 return head;
901 error:
902 FREE(key);
903 FREE(value);
904 if (head != NULL)
905 destroy_entry(head);
906 return NULL;
907 }
908
909
910 /**
911 * A mini-scanner for tokenizing a query string
912 */
get_next_token(char * s,int * cursor,char ** r)913 static int get_next_token(char *s, int *cursor, char **r) {
914 int i = *cursor;
915
916 while (s[*cursor]) {
917 if (s[*cursor+1] == '=') {
918 *cursor += 1;
919 *r = Str_ndup(&s[i], (*cursor-i));
920 return KEY;
921 }
922 if (s[*cursor] == '=') {
923 while (s[*cursor] && s[*cursor] != '&') *cursor += 1;
924 if (s[*cursor] == '&') {
925 *r = Str_ndup(&s[i+1], (*cursor-i)-1);
926 *cursor += 1;
927 } else {
928 *r = Str_ndup(&s[i+1], (*cursor-i));
929 }
930 return VALUE;
931 }
932 *cursor += 1;
933 }
934 return 0;
935 }
936
937