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, "&lt;");
183                 else if (s[i] == '>')
184                         StringBuffer_append(sb, "&gt;");
185                 else if (s[i] == '&')
186                         StringBuffer_append(sb, "&amp;");
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