diff options
27 files changed, 1434 insertions, 1257 deletions
diff --git a/dep/gsoap/httpget.c b/dep/gsoap/httpget.c deleted file mode 100644 index 34eb3960e64..00000000000 --- a/dep/gsoap/httpget.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - httpget.c - - gSOAP HTTP GET plugin. - - See instructions below. - -gSOAP XML Web services tools -Copyright (C) 2000-2008, Robert van Engelen, Genivia Inc., All Rights Reserved. -This part of the software is released under ONE of the following licenses: -GPL or the gSOAP public license. --------------------------------------------------------------------------------- -gSOAP public license. - -The contents of this file are subject to the gSOAP Public License Version 1.3 -(the "License"); you may not use this file except in compliance with the -License. You may obtain a copy of the License at -http://www.cs.fsu.edu/~engelen/soaplicense.html -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -for the specific language governing rights and limitations under the License. - -The Initial Developer of the Original Code is Robert A. van Engelen. -Copyright (C) 2000-2008 Robert A. van Engelen, Genivia inc. All Rights Reserved. --------------------------------------------------------------------------------- -GPL license. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place, Suite 330, Boston, MA 02111-1307 USA - -Author contact information: -engelen@genivia.com / engelen@acm.org - -This program is released under the GPL with the additional exemption that -compiling, linking, and/or using OpenSSL is allowed. --------------------------------------------------------------------------------- - - Compile & link with stand-alone gSOAP server for HTTP GET support. - Compile & link with gSOAP clients for client-side HTTP GET requests. - - Usage (server side): - - struct soap soap; - soap_init(&soap); - soap_register_plugin_arg(&soap, http_get, http_get_handler); - ... - ... = soap_copy(&soap); // copies plugin too but not its data: plugin data is shared since fcopy is not set - ... - soap_done(&soap); // detach plugin (calls plugin->fdelete) - - You need to define a HTTP GET handling function at the server-side: - - int http_get_handler(struct soap*) - - which will be called from the plugin upon an HTTP GET request. - The function should return an error code or SOAP_OK; - This function should produce a valid HTTP response, for example: - - soap_response(soap, SOAP_HTML); // use this to return HTML ... - soap_response(soap, SOAP_OK); // ... or use this to return a SOAP message - ... - soap_send(soap, "<HTML>...</HTML>"); // example HTML - ... - soap_end_send(soap); - - To get query string key-value pairs within a service routine, use: - - char *s; - s = soap_query(soap); - while (s) - { - char *key = soap_query_key(soap, &s); - char *val = soap_query_val(soap, &s); - ... - } - - This will garble soap->path, which contains the HTTP path of the form: - /path?query - where path and/or ?query may be absent. The path info is obtained from - the HTTP request URL: http://domain/path?query - The URL should not exceed the length of SOAP_TAGLEN. Adjust SOAP_TAGLEN - in stdsoap2.h if necessary. - - Usage (client side): - - For SOAP GET method calls, declare the service method with protocol GET: - - //gsoap ns service method-protocol: someMethod GET - int ns__someMethod(... in-params ..., struct ns__someMethodResponse { ... out-params ... } *); - - and to make the call in your code: - - struct ns__someMethodResponse res; - soap_call_ns__someMethod(soap, "endpoint", "action", ... in-params ...., &res) - - Client code for non-SOAP REST GET: - - To use general HTTP GET, for example to retrieve an HTML document, use: - - struct soap soap; - char *buf = NULL; - size_t len = 0; - soap_init(&soap); - if (soap_GET(&soap, endpoint, action) - || soap_begin_recv(&soap)) - ... connect/recv error ... - else - buf = soap_http_get_body(&soap, &len); - soap_end_recv(&soap); - ... process data in buf[0..len-1] - soap_destroy(&soap); - soap_end(&soap); - soap_done(&soap); - - The soap_http_get_body() function above returns the HTTP body content - as a string. - -*/ - -#include "httpget.h" - -#ifdef __cplusplus -extern "C" { -#endif - -const char http_get_id[] = HTTP_GET_ID; - -static int http_get_init(struct soap *soap, struct http_get_data *data, int (*handler)(struct soap*)); -static void http_get_delete(struct soap *soap, struct soap_plugin *p); -static int http_get_parse(struct soap *soap); -static int http_get_handler(struct soap *soap); - -int http_get(struct soap *soap, struct soap_plugin *p, void *arg) -{ - p->id = http_get_id; - p->data = (void*)SOAP_MALLOC(soap, sizeof(struct http_get_data)); - /* p->fcopy = http_get_copy; obsolete, see note with http_get_copy() */ - p->fdelete = http_get_delete; - if (!p->data) - return SOAP_EOM; - if (http_get_init(soap, (struct http_get_data*)p->data, (int (*)(struct soap*))arg)) - { - SOAP_FREE(soap, p->data); /* error: could not init */ - return SOAP_EOM; /* return error */ - } - return SOAP_OK; -} - -static int http_get_init(struct soap *soap, struct http_get_data *data, int (*handler)(struct soap*)) -{ - data->fparse = soap->fparse; /* save old HTTP header parser callback */ - data->fget = handler; - data->stat_get = 0; - data->stat_post = 0; - data->stat_fail = 0; - memset((void*)data->hist_min, 0, sizeof(data->hist_min)); - memset((void*)data->hist_hour, 0, sizeof(data->hist_hour)); - memset((void*)data->hist_day, 0, sizeof(data->hist_day)); - soap->fparse = http_get_parse; /* replace HTTP header parser callback with ours */ - soap->fget = http_get_handler; /* replace HTTP GET callback with ours */ - return SOAP_OK; -} - -/* We will share the plugin data among all soap copies created with soap_copy(), so we don't have to define this -static int http_get_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src) -{ - *dst = *src; - return SOAP_OK; -} -*/ - -static void http_get_delete(struct soap *soap, struct soap_plugin *p) -{ - (void)soap; - SOAP_FREE(soap, p->data); /* free allocated plugin data (this function is not called for shared plugin data, but only when the final soap_done() is invoked on the original soap struct) */ -} - -static int http_get_parse(struct soap *soap) -{ -#ifndef WITH_LEAN - time_t t; -#ifdef HAVE_LOCALTIME_R - struct tm T; -#endif - struct tm *pT; -#endif - struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id); - if (!data) - return SOAP_PLUGIN_ERROR; -#ifndef WITH_LEAN - time(&t); -#ifdef HAVE_LOCALTIME_R - pT = localtime_r(&t, &T); -#else - pT = localtime(&t); -#endif - /* updates should be in mutex, but we don't mind some inaccuracy in the count to preserve performance */ - data->hist_day[pT->tm_yday]++; - data->hist_day[(pT->tm_yday + 1) % 365] = 0; - data->hist_hour[pT->tm_hour]++; - data->hist_hour[(pT->tm_hour + 1) % 24] = 0; - data->hist_min[pT->tm_min]++; - data->hist_min[(pT->tm_min + 1) % 60] = 0; -#endif - soap->error = data->fparse(soap); /* parse HTTP header */ - if (!soap->error) - { - if (soap->status == SOAP_POST) - data->stat_post++; /* update should be in mutex, but we don't mind some inaccuracy in the count */ - } - else - { - data->stat_fail++; /* update should be in mutex, but we don't mind some inaccuracy in the count */ - } - return soap->error; -} - -/******************************************************************************/ - -static int http_get_handler(struct soap *soap) -{ - struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id); - if (!data) - return SOAP_PLUGIN_ERROR; - soap->error = data->fget(soap); /* call user-defined HTTP GET handler */ - if (soap->error) - { - /* update should be in mutex, but we don't mind some inaccuracy in the count */ - data->stat_fail++; - return soap->error; - } - /* update should be in mutex, but we don't mind some inaccuracy in the count */ - data->stat_get++; - return SOAP_OK; -} - -/******************************************************************************/ - -void soap_http_get_stats(struct soap *soap, size_t *stat_get, size_t *stat_post, size_t *stat_fail, size_t **hist_min, size_t **hist_hour, size_t **hist_day) -{ - struct http_get_data *data = (struct http_get_data*)soap_lookup_plugin(soap, http_get_id); - if (!data) - return; - if (stat_get) - *stat_get = data->stat_get; - if (stat_post) - *stat_post = data->stat_post; - if (stat_fail) - *stat_fail = data->stat_fail; - if (hist_min) - *hist_min = data->hist_min; - if (hist_hour) - *hist_hour = data->hist_hour; - if (hist_day) - *hist_day = data->hist_day; -} - -/* deprecated: use soap_GET instead */ -int soap_http_get_connect(struct soap *soap, const char *endpoint, const char *action) -{ - return soap_GET(soap, endpoint, action); -} - -#ifdef __cplusplus -} -#endif - diff --git a/dep/gsoap/httpget.h b/dep/gsoap/httpget.h deleted file mode 100644 index 02b8bf24408..00000000000 --- a/dep/gsoap/httpget.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - httpget.h - - gSOAP HTTP GET plugin. - - See httpget.c for usage instructions. - -gSOAP XML Web services tools -Copyright (C) 2000-2008, Robert van Engelen, Genivia Inc., All Rights Reserved. -This part of the software is released under ONE of the following licenses: -GPL or the gSOAP public license. --------------------------------------------------------------------------------- -gSOAP public license. - -The contents of this file are subject to the gSOAP Public License Version 1.3 -(the "License"); you may not use this file except in compliance with the -License. You may obtain a copy of the License at -http://www.cs.fsu.edu/~engelen/soaplicense.html -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -for the specific language governing rights and limitations under the License. - -The Initial Developer of the Original Code is Robert A. van Engelen. -Copyright (C) 2000-2008 Robert A. van Engelen, Genivia inc. All Rights Reserved. --------------------------------------------------------------------------------- -GPL license. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place, Suite 330, Boston, MA 02111-1307 USA - -Author contact information: -engelen@genivia.com / engelen@acm.org - -This program is released under the GPL with the additional exemption that -compiling, linking, and/or using OpenSSL is allowed. --------------------------------------------------------------------------------- -*/ - -#ifndef HTTPGET_H -#define HTTPGET_H - -#include "stdsoap2.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define HTTP_GET_ID "SOAP-HTTP-GET/2.1" /* plugin identification */ - -extern const char http_get_id[]; - -/* This is the local plugin data shared among all copies of the soap struct: */ -struct http_get_data -{ - int (*fparse)(struct soap*); /* to save and call the internal HTTP header parser */ - int (*fget)(struct soap*); /* user-defined server-side HTTP GET handler */ - size_t stat_get; /* HTTP GET usage statistics */ - size_t stat_post; /* HTTP POST usage statistics */ - size_t stat_fail; /* HTTP failure statistics */ - size_t hist_min[60]; /* Hits by the minute */ - size_t hist_hour[24]; /* Hits by the hour */ - size_t hist_day[366]; /* Hits by day */ -}; - -int http_get(struct soap*, struct soap_plugin*, void*); -int soap_http_get_connect(struct soap*, const char*, const char*); - -void soap_http_get_stats(struct soap *soap, size_t *stat_get, size_t *stat_post, size_t *stat_fail, size_t **hist_min, size_t **hist_hour, size_t **hist_day); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/dep/gsoap/httppost.c b/dep/gsoap/httppost.c deleted file mode 100644 index 6219ecbf33b..00000000000 --- a/dep/gsoap/httppost.c +++ /dev/null @@ -1,368 +0,0 @@ -/* - httppost.c - - gSOAP HTTP POST/PUT/DELETE plugin for SOAP and non-SOAP payloads. - - See instructions below. - - Revisions: - register multiple POST content handlers, each for a content type - - Note: multipart/related and multipart/form-data are already handled in gSOAP. - -gSOAP XML Web services tools -Copyright (C) 2000-2018, Robert van Engelen, Genivia, Inc. All Rights Reserved. - --------------------------------------------------------------------------------- -gSOAP public license. - -The contents of this file are subject to the gSOAP Public License Version 1.3 -(the "License"); you may not use this file except in compliance with the -License. You may obtain a copy of the License at -http://www.cs.fsu.edu/~engelen/soaplicense.html -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -for the specific language governing rights and limitations under the License. - -The Initial Developer of the Original Code is Robert A. van Engelen. -Copyright (C) 2000-2018 Robert A. van Engelen, Genivia inc. All Rights Reserved. --------------------------------------------------------------------------------- -GPL license. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place, Suite 330, Boston, MA 02111-1307 USA - -Author contact information: -engelen@genivia.com / engelen@acm.org - -This program is released under the GPL with the additional exemption that -compiling, linking, and/or using OpenSSL is allowed. --------------------------------------------------------------------------------- - - Compile & link with stand-alone gSOAP server. - - Usage (server side): - - Define a NULL-terminated array of type-handler pairs, each with a media - type and a the handler function: - - struct http_post_handlers my_handlers[] = { - { "application/json", json_post_handler }, - { "image/jpg", jpeg_post_handler }, - { "text/html", html_post_handler }, - { "POST", generic_POST_handler }, - { "PUT", generic_PUT_handler }, - { "PATCH", generic_PATCH_handler }, - { "DELETE", generic_DELETE_handler }, - { NULL } - }; - - A `generic_POST_handler`, when specified with a `"POST"` key entry in - the table, takes priority over `soap_serve()` if no `SOAPAction` HTTP - header is included in the message. This means that SOAP/XML messages - without a `SOAPAction` header will not be processed by `soap_serve()`! - - Note that `*` and `-` can be used as wildcards to match any text and - any character, respectively: - - { "image*", image_post_handler }, - { "text*", text_post_handler }, - { "text/---", three_char_type_text_handler }, - - In the above, to be more accurate, we should use a slash / between - image and the wildcard * (which is not shown in the table above due to - compilers throwing a fit at the / and * combo in this comment block). - - It is possible to specify other paramters that must match: - - { "text/html; charset=utf-8", html_post_handler }, - - Media types may have optional parameters after `;` such as `charset` - and `boundary`. These parameters can be matched by the media type - patterns in the table. Patterns that are more specific must precede - patterns that are less specific in the table. For example, - `"text/xml;*charset=utf-8*"` must precede `"text/xml"` which must - precede `"text*"`. Note that `"text/xml"` also matches any parameters - of the media type of the message reveived, such as `"text/xml; - charset=utf-8"` (only since gSOAP version 2.8.75). - - Each handler is a function that takes the soap context as a parameter - and returns SOAP_OK or an error code. - - Register the plugin and the handlers: - - struct soap soap; - soap_init(&soap); - soap_register_plugin_arg(&soap, http_post, my_handlers); - ... - ... = soap_copy(&soap); // copies plugin too but not its data: plugin data is shared since fcopy is not set - ... - soap_done(&soap); // delete plugin (calls plugin->fdelete) - - A POST handler function is triggered by the media type in the - http_post_handlers table. Use http_http_get_body() as below to retrieve - HTTP POST body data: - - int image_post_handler(struct soap *soap) - { - const char *buf; - size_t len; - // if necessary, check type in soap->http_content - if (soap->http_content && soap_tag_cmp(soap->http_content, "image/gif")) - return 404; - buf = soap_http_get_body(soap, &len); - if (!buf) - return soap->error; - soap_end_recv(soap); - ... // process image in buf[0..len-1] - // reply with empty HTTP OK response: - return soap_send_empty_response(soap, SOAP_OK); - } - - This function should also produce a valid HTTP response, for example: - - if (we want to return HTML) - { - if (soap_response(soap, SOAP_HTML) // use this to return HTML... - || soap_send(soap, "<HTML>...</HTML>"); // example HTML - || ... - || soap_end_send(soap)) - return soap_closesock(soap); - return SOAP_OK; - } - else - { - soap->http_content = "image/jpeg"; // a jpeg image - if (soap_response(soap, SOAP_FILE) // SOAP_FILE sets custom http content - || soap_send_raw(soap, ..., ...); - || ... - || soap_end_send(soap)) - return soap_closesock(soap); - return SOAP_OK; - } - - The soap_closesock() call only closes the connection when it shoud not - be kept alive. This function is called automatically after the handler - returns. There is no harm in calling this function more than once. - - The soap_send(soap, char*) and soap_send_raw(soap, char*, size_t) can - be used to return content from server. - - The soap_response(soap, SOAP_FILE) call returns a HTTP 200 OK response - with the HTTP content-type header set to soap->http_content. To return - a http error status code use soap_response(soap, SOAP_FILE + status) - with status between 200 and 599 or 0. - - Usage (client side): - - char *buf; // to hold the HTTP response body data - size_t len; - ... - if (soap_post_connect(soap, "URL", "SOAP action or NULL", "media type") - || soap_send(soap, ...) - || soap_end_send(soap)) - ... // error - if (soap_begin_recv(&soap) - || soap_http_body(&soap, &buf, &len) - || soap_end_recv(&soap)) - ... // error - ... // use buf[0..len-1] - soap_closesock(soap); // close, but keep open only with HTTP keep-alive - soap_end(soap); // also deletes buf content - - The soap_send(soap, char*) and soap_send_raw(soap, char*, size_t) can - be used to send content to the server as shown above. - -*/ - -#include "httppost.h" - -#ifdef __cplusplus -extern "C" { -#endif - -const char http_post_id[] = HTTP_POST_ID; - -static int http_post_init(struct soap *soap, struct http_post_data *data, struct http_post_handlers *handlers); -static void http_post_delete(struct soap *soap, struct soap_plugin *p); -static int http_post_parse_header(struct soap *soap, const char*, const char*); -static http_handler_t http_lookup_handler(struct soap *soap, const char *type, struct http_post_data *data); - -static int http_fput(struct soap *soap); -static int http_fpatch(struct soap *soap); -static int http_fdel(struct soap *soap); - -int http_post(struct soap *soap, struct soap_plugin *p, void *arg) -{ - p->id = http_post_id; - p->data = (void*)SOAP_MALLOC(soap, sizeof(struct http_post_data)); - p->fdelete = http_post_delete; - if (!p->data) - return SOAP_EOM; - if (http_post_init(soap, (struct http_post_data*)p->data, (struct http_post_handlers *)arg)) - { - SOAP_FREE(soap, p->data); /* error: could not init */ - return SOAP_EOM; /* return error */ - } - return SOAP_OK; -} - -static int http_post_init(struct soap *soap, struct http_post_data *data, struct http_post_handlers *handlers) -{ - data->fparsehdr = soap->fparsehdr; /* save old HTTP header parser callback */ - soap->fparsehdr = http_post_parse_header; /* replace HTTP header parser callback with ours */ - data->fput = soap->fput; - soap->fput = http_fput; - data->fpatch = soap->fpatch; - soap->fpatch = http_fpatch; - data->fdel = soap->fdel; - soap->fdel = http_fdel; - data->handlers = handlers; - return SOAP_OK; -} - -static void http_post_delete(struct soap *soap, struct soap_plugin *p) -{ - soap->fparsehdr = ((struct http_post_data*)p->data)->fparsehdr; - soap->fput = ((struct http_post_data*)p->data)->fput; - soap->fpatch = ((struct http_post_data*)p->data)->fpatch; - soap->fdel = ((struct http_post_data*)p->data)->fdel; - SOAP_FREE(soap, p->data); /* free allocated plugin data (this function is not called for shared plugin data, but only when the final soap_done() is invoked on the original soap struct) */ -} - -static int http_post_parse_header(struct soap *soap, const char *key, const char *val) -{ - struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id); - if (!data) - return SOAP_PLUGIN_ERROR; - if (data->fparsehdr(soap, key, val)) /* parse HTTP header */ - return soap->error; - if (!soap_tag_cmp(key, "Content-Type")) /* check content type */ - soap->fform = http_lookup_handler(soap, val, data); - if (!soap->fform) - soap->fform = http_lookup_handler(soap, "POST", data); - return soap->error; -} - -static http_handler_t http_lookup_handler(struct soap *soap, const char *type, struct http_post_data *data) -{ - struct http_post_handlers *p; - const char *params = strchr(type, ';'); - char temp[SOAP_HDRLEN]; - (void)soap; - if (params) - { - size_t n = params - type; - soap_strncpy(temp, sizeof(temp), type, n); - temp[n] = '\0'; - } - for (p = data->handlers; p && p->type; p++) - { - if (params) - { - if (strchr(p->type, ';')) - { - if (!soap_tag_cmp(type, p->type)) - break; - } - else if (!soap_tag_cmp(temp, p->type)) - { - break; - } - } - else if (!soap_tag_cmp(type, p->type)) - { - break; - } - } - if (p && p->type) - { - DBGLOG(TEST,SOAP_MESSAGE(fdebug, "Found HTTP POST plugin handler for '%s'\n", type)); - return p->handler; - } - return NULL; -} - -static int http_fput(struct soap *soap) -{ - int (*fform)(struct soap*); - struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id); - if (!data) - return SOAP_PLUGIN_ERROR; - fform = http_lookup_handler(soap, "PUT", data); - if (fform) - return fform(soap); - return data->fput(soap); -} - -static int http_fpatch(struct soap *soap) -{ - int (*fform)(struct soap*); - struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id); - if (!data) - return SOAP_PLUGIN_ERROR; - fform = http_lookup_handler(soap, "PATCH", data); - if (fform) - return fform(soap); - return data->fpatch(soap); -} - -static int http_fdel(struct soap *soap) -{ - int (*fform)(struct soap*); - struct http_post_data *data = (struct http_post_data*)soap_lookup_plugin(soap, http_post_id); - if (!data) - return SOAP_PLUGIN_ERROR; - fform = http_lookup_handler(soap, "DELETE", data); - if (fform) - return fform(soap); - return data->fdel(soap); -} - -/******************************************************************************/ - -/* call soap_http_get_body to receive HTTP body and set buf and len */ -int soap_http_body(struct soap *soap, char **buf, size_t *len) -{ - *buf = soap_http_get_body(soap, len); - if (*buf) - return SOAP_OK; - return soap->error; -} - -/******************************************************************************/ - -/* deprecated: use soap_POST instead */ -int soap_post_connect(struct soap *soap, const char *endpoint, const char *action, const char *type) -{ - return soap_POST(soap, endpoint, action, type); -} - -/* deprecated: use soap_PUT instead */ -int soap_put_connect(struct soap *soap, const char *endpoint, const char *action, const char *type) -{ - return soap_PUT(soap, endpoint, action, type); -} - -/* deprecated: use soap_DELETE instead */ -int soap_delete_connect(struct soap *soap, const char *endpoint) -{ - return soap_DELETE(soap, endpoint); -} - -/******************************************************************************/ - -#ifdef __cplusplus -} -#endif - diff --git a/dep/gsoap/httppost.h b/dep/gsoap/httppost.h deleted file mode 100644 index 9747ff0a923..00000000000 --- a/dep/gsoap/httppost.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - httppost.h - - gSOAP HTTP POST plugin for non-SOAP payloads. - - See httppost.c for instructions. - - Revisions: - register multiple POST content handlers, each for a content type - -gSOAP XML Web services tools -Copyright (C) 2000-2018, Robert van Engelen, Genivia, Inc. All Rights Reserved. - --------------------------------------------------------------------------------- -gSOAP public license. - -The contents of this file are subject to the gSOAP Public License Version 1.3 -(the "License"); you may not use this file except in compliance with the -License. You may obtain a copy of the License at -http://www.cs.fsu.edu/~engelen/soaplicense.html -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -for the specific language governing rights and limitations under the License. - -The Initial Developer of the Original Code is Robert A. van Engelen. -Copyright (C) 2000-2004 Robert A. van Engelen, Genivia inc. All Rights Reserved. --------------------------------------------------------------------------------- -GPL license. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 2 of the License, or (at your option) any later -version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place, Suite 330, Boston, MA 02111-1307 USA - -Author contact information: -engelen@genivia.com / engelen@acm.org - -This program is released under the GPL with the additional exemption that -compiling, linking, and/or using OpenSSL is allowed. --------------------------------------------------------------------------------- -*/ - -#ifndef HTTPPOST_H -#define HTTPPOST_H - -#include "stdsoap2.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define HTTP_POST_ID "SOAP-HTTP-POST/2.2" /* plugin identification */ - -extern const char http_post_id[]; - -typedef int (*http_handler_t)(struct soap*); - -struct http_post_handlers -{ - const char *type; - http_handler_t handler; -}; - -/* This is the local plugin data shared among all copies of the soap struct: */ -struct http_post_data -{ - int (*fparsehdr)(struct soap*, const char*, const char*); /* to save and call the internal HTTP header parser */ - int (*fput)(struct soap*); /* to save */ - int (*fpatch)(struct soap*); /* to save */ - int (*fdel)(struct soap*); /* to save */ - struct http_post_handlers *handlers; /* the server-side POST content type handlers */ -}; - -/* the http post plugin, note: argument should be a table of type-handler pairs */ -int http_post(struct soap*, struct soap_plugin*, void*); - -/* deprecated: use soap_POST instead */ -int soap_post_connect(struct soap*, const char *endpoint, const char *action, const char *type); - -/* deprecated: use soap_PUT instead */ -int soap_put_connect(struct soap*, const char *endpoint, const char *action, const char *type); - -/* deprecated: use soap_DELETE instead */ -int soap_delete_connect(struct soap*, const char *endpoint); - -/* deprecated: use soap_get_http_body instead */ -int soap_http_body(struct soap*, char **buf, size_t *len); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/common/Cryptography/Authentication/SRP6.cpp b/src/common/Cryptography/Authentication/SRP6.cpp index a2266f35f71..c172bbc1d3d 100644 --- a/src/common/Cryptography/Authentication/SRP6.cpp +++ b/src/common/Cryptography/Authentication/SRP6.cpp @@ -24,12 +24,7 @@ using SHA1 = Trinity::Crypto::SHA1; using SRP6 = Trinity::Crypto::SRP6; -/*static*/ std::array<uint8, 1> const SRP6::g = []() -{ - std::array<uint8, 1> g_temp; - g_temp[0] = 7; - return g_temp; -}(); +/*static*/ std::array<uint8, 1> const SRP6::g = { 7 }; /*static*/ std::array<uint8, 32> const SRP6::N = HexStrToByteArray<32>("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", true); /*static*/ BigNumber const SRP6::_g(SRP6::g); /*static*/ BigNumber const SRP6::_N(N); diff --git a/src/common/Utilities/Concepts.h b/src/common/Utilities/Concepts.h new file mode 100644 index 00000000000..4cfa52c029c --- /dev/null +++ b/src/common/Utilities/Concepts.h @@ -0,0 +1,33 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_CONCEPTS_H +#define TRINITYCORE_CONCEPTS_H + +#include <concepts> +#include <functional> // std::invoke + +namespace Trinity +{ +template <typename Callable, typename R, typename... Args> +concept invocable_r = requires(Callable && callable, Args&&... args) +{ + { std::invoke(static_cast<Callable&&>(callable), static_cast<Args&&>(args)...) } -> std::convertible_to<R>; +}; +} + +#endif // TRINITYCORE_CONCEPTS_H diff --git a/src/server/bnetserver/Main.cpp b/src/server/bnetserver/Main.cpp index e1168a1a90b..6d667cd48b1 100644 --- a/src/server/bnetserver/Main.cpp +++ b/src/server/bnetserver/Main.cpp @@ -200,21 +200,29 @@ int main(int argc, char** argv) Trinity::Net::ScanLocalNetworks(); - // Start the listening port (acceptor) for auth connections - int32 bnport = sConfigMgr->GetIntDefault("BattlenetPort", 1119); - if (bnport < 0 || bnport > 0xFFFF) + std::string httpBindIp = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0"); + int32 httpPort = sConfigMgr->GetIntDefault("LoginREST.Port", 8081); + if (httpPort <= 0 || httpPort > 0xFFFF) { - TC_LOG_ERROR("server.bnetserver", "Specified battle.net port ({}) out of allowed range (1-65535)", bnport); + TC_LOG_ERROR("server.bnetserver", "Specified login service port ({}) out of allowed range (1-65535)", httpPort); return 1; } - if (!sLoginService.Start(ioContext.get())) + if (!sLoginService.StartNetwork(*ioContext, httpBindIp, httpPort)) { TC_LOG_ERROR("server.bnetserver", "Failed to initialize login service"); return 1; } - std::shared_ptr<void> sLoginServiceHandle(nullptr, [](void*) { sLoginService.Stop(); }); + std::shared_ptr<void> sLoginServiceHandle(nullptr, [](void*) { sLoginService.StopNetwork(); }); + + // Start the listening port (acceptor) for auth connections + int32 bnport = sConfigMgr->GetIntDefault("BattlenetPort", 1119); + if (bnport <= 0 || bnport > 0xFFFF) + { + TC_LOG_ERROR("server.bnetserver", "Specified battle.net port ({}) out of allowed range (1-65535)", bnport); + return 1; + } // Get the list of realms for the server sRealmList->Initialize(*ioContext, sConfigMgr->GetIntDefault("RealmsStateUpdateDelay", 10)); diff --git a/src/server/bnetserver/REST/LoginHttpSession.cpp b/src/server/bnetserver/REST/LoginHttpSession.cpp new file mode 100644 index 00000000000..95112cb8836 --- /dev/null +++ b/src/server/bnetserver/REST/LoginHttpSession.cpp @@ -0,0 +1,118 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "LoginHttpSession.h" +#include "DatabaseEnv.h" +#include "LoginRESTService.h" +#include "SslContext.h" +#include "Util.h" + +namespace Battlenet +{ +LoginHttpSession::LoginHttpSession(boost::asio::ip::tcp::socket&& socket) + : SslSocket(std::move(socket), SslContext::instance()) +{ +} + +LoginHttpSession::~LoginHttpSession() = default; + +void LoginHttpSession::Start() +{ + std::string ip_address = GetRemoteIpAddress().to_string(); + TC_LOG_TRACE("server.http.session", "{} Accepted connection", GetClientInfo()); + + // Verify that this IP is not in the ip_banned table + LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO); + stmt->setString(0, ip_address); + + _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt) + .WithPreparedCallback([sess = shared_from_this()](PreparedQueryResult result) { sess->CheckIpCallback(std::move(result)); })); +} + +void LoginHttpSession::CheckIpCallback(PreparedQueryResult result) +{ + if (result) + { + bool banned = false; + do + { + Field* fields = result->Fetch(); + if (fields[0].GetUInt64() != 0) + banned = true; + + } while (result->NextRow()); + + if (banned) + { + TC_LOG_DEBUG("server.http.session", "{} tries to log in using banned IP!", GetClientInfo()); + CloseSocket(); + return; + } + } + + AsyncHandshake(); +} + +Trinity::Net::Http::RequestHandlerResult LoginHttpSession::RequestHandler(Trinity::Net::Http::RequestContext& context) +{ + return sLoginService.HandleRequest(shared_from_this(), context); +} + +std::shared_ptr<Trinity::Net::Http::SessionState> LoginHttpSession::ObtainSessionState(Trinity::Net::Http::RequestContext& context) const +{ + using namespace std::string_literals; + + std::shared_ptr<Trinity::Net::Http::SessionState> state; + + auto cookieItr = context.request.find(boost::beast::http::field::cookie); + if (cookieItr != context.request.end()) + { + std::vector<std::string_view> cookies = Trinity::Tokenize(Trinity::Net::Http::ToStdStringView(cookieItr->value()), ';', false); + std::size_t eq = 0; + auto sessionIdItr = std::find_if(cookies.begin(), cookies.end(), [&](std::string_view cookie) + { + std::string_view name = cookie; + eq = cookie.find('='); + if (eq != std::string_view::npos) + name = cookie.substr(0, eq); + + return name == SESSION_ID_COOKIE; + }); + if (sessionIdItr != cookies.end()) + { + std::string_view value = sessionIdItr->substr(eq + 1); + state = sLoginService.FindAndRefreshSessionState(value, GetRemoteIpAddress()); + } + } + + if (!state) + { + state = sLoginService.CreateNewSessionState(GetRemoteIpAddress()); + + std::string_view host = Trinity::Net::Http::ToStdStringView(context.request[boost::beast::http::field::host]); + if (std::size_t port = host.find(':'); port != std::string_view::npos) + host.remove_suffix(host.length() - port); + + context.response.insert(boost::beast::http::field::set_cookie, Trinity::StringFormat("{}={}; Path=/bnetserver; Domain={}; Secure; HttpOnly; SameSite=None", + SESSION_ID_COOKIE, boost::uuids::to_string(state->Id), host)); + } + + return state; +} +} diff --git a/src/server/bnetserver/REST/LoginHttpSession.h b/src/server/bnetserver/REST/LoginHttpSession.h new file mode 100644 index 00000000000..17c94b55bda --- /dev/null +++ b/src/server/bnetserver/REST/LoginHttpSession.h @@ -0,0 +1,43 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_LOGIN_HTTP_SESSION_H +#define TRINITYCORE_LOGIN_HTTP_SESSION_H + +#include "HttpSslSocket.h" + +namespace Battlenet +{ +class LoginHttpSession : public Trinity::Net::Http::SslSocket<LoginHttpSession> +{ +public: + static constexpr std::string_view SESSION_ID_COOKIE = "JSESSIONID"; + + explicit LoginHttpSession(boost::asio::ip::tcp::socket&& socket); + ~LoginHttpSession(); + + void Start() override; + + void CheckIpCallback(PreparedQueryResult result); + + Trinity::Net::Http::RequestHandlerResult RequestHandler(Trinity::Net::Http::RequestContext& context) override; + +protected: + std::shared_ptr<Trinity::Net::Http::SessionState> ObtainSessionState(Trinity::Net::Http::RequestContext& context) const override; +}; +} +#endif // TRINITYCORE_LOGIN_HTTP_SESSION_H diff --git a/src/server/bnetserver/REST/LoginRESTService.cpp b/src/server/bnetserver/REST/LoginRESTService.cpp index 6141dc702f9..602a38dceed 100644 --- a/src/server/bnetserver/REST/LoginRESTService.cpp +++ b/src/server/bnetserver/REST/LoginRESTService.cpp @@ -16,71 +16,63 @@ */ #include "LoginRESTService.h" +#include "Base64.h" #include "Configuration/Config.h" #include "CryptoHash.h" #include "CryptoRandom.h" #include "DatabaseEnv.h" -#include "Errors.h" #include "IpNetwork.h" +#include "IteratorPair.h" #include "ProtobufJSON.h" #include "Resolver.h" -#include "SslContext.h" #include "Util.h" -#include "httpget.h" -#include "httppost.h" -#include "soapH.h" +#include <boost/uuid/string_generator.hpp> +#include <fmt/chrono.h> -int ns1__executeCommand(soap*, char*, char**) { return SOAP_OK; } +namespace Battlenet +{ +LoginRESTService& LoginRESTService::Instance() +{ + static LoginRESTService instance; + return instance; +} -class AsyncRequest +bool LoginRESTService::StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount) { -public: - AsyncRequest(soap const& server) : _client(server), _responseStatus(0) { } + if (!HttpService::StartNetwork(ioContext, bindIp, port, threadCount)) + return false; - AsyncRequest(AsyncRequest const&) = delete; - AsyncRequest& operator=(AsyncRequest const&) = delete; - AsyncRequest(AsyncRequest&&) = default; - AsyncRequest& operator=(AsyncRequest&&) = default; + using Trinity::Net::Http::RequestHandlerFlag; - bool InvokeIfReady() + RegisterHandler(boost::beast::http::verb::get, "/bnetserver/login/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) { - ASSERT(_callback); - return _callback->InvokeIfReady(); - } - - soap* GetClient() { return &_client; } - void SetCallback(std::unique_ptr<QueryCallback> callback) { _callback = std::move(callback); } - int32 GetResponseStatus() const { return _responseStatus; } - void SetResponseStatus(int32 responseStatus) { _responseStatus = responseStatus; } + return HandleGetForm(std::move(session), context); + }); -private: - soap _client; - std::unique_ptr<QueryCallback> _callback; - int32 _responseStatus; -}; + RegisterHandler(boost::beast::http::verb::get, "/bnetserver/gameAccounts/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) + { + return HandleGetGameAccounts(std::move(session), context); + }); -int32 handle_get_plugin(soap* soapClient) -{ - return sLoginService.HandleHttpRequest(soapClient, "GET", sLoginService._getHandlers); -} + RegisterHandler(boost::beast::http::verb::get, "/bnetserver/portal/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) + { + return HandleGetPortal(std::move(session), context); + }); -int32 handle_post_plugin(soap* soapClient) -{ - return sLoginService.HandleHttpRequest(soapClient, "POST", sLoginService._postHandlers); -} + RegisterHandler(boost::beast::http::verb::post, "/bnetserver/login/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) + { + return HandlePostLogin(std::move(session), context); + }, RequestHandlerFlag::DoNotLogRequestContent); -bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext) -{ - _ioContext = ioContext; - _bindIP = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0"); - _port = sConfigMgr->GetIntDefault("LoginREST.Port", 8081); - if (_port < 0 || _port > 0xFFFF) + RegisterHandler(boost::beast::http::verb::post, "/bnetserver/refreshLoginTicket/", [this](std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) { - TC_LOG_ERROR("server.rest", "Specified login service port ({}) out of allowed range (1-65535), defaulting to 8081", _port); - _port = 8081; - } + return HandlePostRefreshLoginTicket(std::move(session), context); + }); - Trinity::Asio::Resolver resolver(*ioContext); + _bindIP = bindIp; + _port = port; + + Trinity::Asio::Resolver resolver(ioContext); _hostnames[0] = sConfigMgr->GetStringDefault("LoginREST.ExternalAddress", "127.0.0.1"); Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), _hostnames[0], std::to_string(_port)); @@ -103,8 +95,8 @@ bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext) _addresses[1] = localAddress->address(); // set up form inputs - Battlenet::JSON::Login::FormInput* input; - _formInputs.set_type(Battlenet::JSON::Login::LOGIN_FORM); + JSON::Login::FormInput* input; + _formInputs.set_type(JSON::Login::LOGIN_FORM); input = _formInputs.add_inputs(); input->set_input_id("account_name"); input->set_type("text"); @@ -124,16 +116,10 @@ bool LoginRESTService::Start(Trinity::Asio::IoContext* ioContext) _loginTicketDuration = sConfigMgr->GetIntDefault("LoginREST.TicketDuration", 3600); - _thread = std::thread(&LoginRESTService::Run, this); + _acceptor->AsyncAcceptWithCallback<&LoginRESTService::OnSocketAccept>(); return true; } -void LoginRESTService::Stop() -{ - _stopped = true; - _thread.join(); -} - std::string const& LoginRESTService::GetHostnameForClient(boost::asio::ip::address const& address) const { if (auto addressIndex = Trinity::Net::SelectAddressForClient(address, _addresses)) @@ -145,117 +131,53 @@ std::string const& LoginRESTService::GetHostnameForClient(boost::asio::ip::addre return _hostnames[0]; } -void LoginRESTService::Run() +std::string LoginRESTService::ExtractAuthorization(HttpRequest const& request) { - soap soapServer(SOAP_C_UTFSTRING, SOAP_C_UTFSTRING); - - // check every 3 seconds if world ended - soapServer.accept_timeout = 3; - soapServer.recv_timeout = 5; - soapServer.send_timeout = 5; - if (!soap_valid_socket(soap_bind(&soapServer, _bindIP.c_str(), _port, 100))) - { - TC_LOG_ERROR("server.rest", "Couldn't bind to {}:{}", _bindIP, _port); - return; - } - - TC_LOG_INFO("server.rest", "Login service bound to http://{}:{}", _bindIP, _port); - - http_post_handlers handlers[] = - { - { "application/json;charset=utf-8", handle_post_plugin }, - { "application/json", handle_post_plugin }, - { nullptr, nullptr } - }; + using namespace std::string_view_literals; - _getHandlers["/bnetserver/login/"] = &LoginRESTService::HandleGetForm; - _getHandlers["/bnetserver/gameAccounts/"] = &LoginRESTService::HandleGetGameAccounts; - _getHandlers["/bnetserver/portal/"] = &LoginRESTService::HandleGetPortal; + std::string ticket; + auto itr = request.find(boost::beast::http::field::authorization); + if (itr == request.end()) + return ticket; - _postHandlers["/bnetserver/login/"] = &LoginRESTService::HandlePostLogin; - _postHandlers["/bnetserver/refreshLoginTicket/"] = &LoginRESTService::HandlePostRefreshLoginTicket; + std::string_view authorization = Trinity::Net::Http::ToStdStringView(itr->value()); + constexpr std::string_view BASIC_PREFIX = "Basic "sv; - soap_register_plugin_arg(&soapServer, &http_get, (void*)&handle_get_plugin); - soap_register_plugin_arg(&soapServer, &http_post, handlers); - soap_register_plugin_arg(&soapServer, &ContentTypePlugin::Init, (void*)"application/json;charset=utf-8"); - soap_register_plugin_arg(&soapServer, &ResponseCodePlugin::Init, nullptr); + if (authorization.starts_with(BASIC_PREFIX)) + authorization.remove_prefix(BASIC_PREFIX.length()); - // Use our already ready ssl context - soapServer.ctx = Battlenet::SslContext::instance().native_handle(); - soapServer.ssl_flags = SOAP_SSL_RSA; + Optional<std::vector<uint8>> decoded = Trinity::Encoding::Base64::Decode(authorization); + if (!decoded) + return ticket; - while (!_stopped) - { - if (!soap_valid_socket(soap_accept(&soapServer))) - continue; // ran into an accept timeout - - std::shared_ptr<AsyncRequest> soapClient = std::make_shared<AsyncRequest>(soapServer); - if (soap_ssl_accept(soapClient->GetClient()) != SOAP_OK) - { - TC_LOG_DEBUG("server.rest", "Failed SSL handshake from IP={}", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string()); - continue; - } - - TC_LOG_DEBUG("server.rest", "Accepted connection from IP={}", boost::asio::ip::address_v4(soapClient->GetClient()->ip).to_string()); - - Trinity::Asio::post(*_ioContext, [soapClient]() - { - soapClient->GetClient()->user = (void*)&soapClient; // this allows us to make a copy of pointer inside GET/POST handlers to increment reference count - soap_begin(soapClient->GetClient()); - soap_begin_recv(soapClient->GetClient()); - }); - } - - // and release the context handle here - soap does not own it so it should not free it on exit - soapServer.ctx = nullptr; - - TC_LOG_INFO("server.rest", "Login service exiting..."); -} - -int32 LoginRESTService::HandleHttpRequest(soap* soapClient, char const* method, HttpMethodHandlerMap const& handlers) -{ - TC_LOG_DEBUG("server.rest", "[{}:{}] Handling {} request path=\"{}\"", - boost::asio::ip::address_v4(soapClient->ip).to_string(), soapClient->port, method, soapClient->path); - - size_t pathLength = strlen(soapClient->path); - if (char const* queryPart = strchr(soapClient->path, '?')) - pathLength = queryPart - soapClient->path; + std::string_view decodedHeader(reinterpret_cast<char const*>(decoded->data()), decoded->size()); - auto handler = handlers.find(std::string{ soapClient->path, pathLength }); - if (handler != handlers.end()) - { - int32 status = (this->*handler->second)(*reinterpret_cast<std::shared_ptr<AsyncRequest>*>(soapClient->user)); - if (status != SOAP_OK) - { - ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = status; - return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse()); - } + if (std::size_t ticketEnd = decodedHeader.find(':'); ticketEnd != std::string_view::npos) + decodedHeader.remove_suffix(decodedHeader.length() - ticketEnd); - return SOAP_OK; - } - - ResponseCodePlugin::GetForClient(soapClient)->ErrorCode = 404; - return SendResponse(soapClient, Battlenet::JSON::Login::ErrorResponse()); + ticket = decodedHeader; + return ticket; } -int32 LoginRESTService::HandleGetForm(std::shared_ptr<AsyncRequest> request) +LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetForm(std::shared_ptr<LoginHttpSession> /*session*/, HttpRequestContext& context) { - return SendResponse(request->GetClient(), _formInputs); + context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8"); + context.response.body() = ::JSON::Serialize(_formInputs); + return RequestHandlerResult::Handled; } -int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> request) +LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) { - if (!request->GetClient()->userid) - return 401; - - request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] { - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_GAME_ACCOUNT_LIST); - stmt->setString(0, request->GetClient()->userid); - return stmt; - }()) - .WithPreparedCallback([this, request](PreparedQueryResult result) + std::string ticket = ExtractAuthorization(context.request); + if (ticket.empty()) + return HandleUnauthorized(std::move(session), context); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_GAME_ACCOUNT_LIST); + stmt->setString(0, ticket); + session->QueueQuery(LoginDatabase.AsyncQuery(stmt) + .WithPreparedCallback([session, context = std::move(context)](PreparedQueryResult result) mutable { - Battlenet::JSON::Login::GameAccountList response; + JSON::Login::GameAccountList gameAccounts; if (result) { auto formatDisplayName = [](char const* name) -> std::string @@ -270,7 +192,7 @@ int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> requ do { Field* fields = result->Fetch(); - Battlenet::JSON::Login::GameAccountInfo* gameAccount = response.add_game_accounts(); + JSON::Login::GameAccountInfo* gameAccount = gameAccounts.add_game_accounts(); gameAccount->set_display_name(formatDisplayName(fields[0].GetCString())); gameAccount->set_expansion(fields[1].GetUInt8()); if (!fields[2].IsNull()) @@ -285,40 +207,37 @@ int32 LoginRESTService::HandleGetGameAccounts(std::shared_ptr<AsyncRequest> requ } while (result->NextRow()); } - SendResponse(request->GetClient(), response); - }))); - - Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); }); + context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8"); + context.response.body() = ::JSON::Serialize(gameAccounts); + session->SendResponse(context); + })); - return SOAP_OK; + return RequestHandlerResult::Async; } -int32 LoginRESTService::HandleGetPortal(std::shared_ptr<AsyncRequest> request) +LoginRESTService::RequestHandlerResult LoginRESTService::HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) { - std::string const& hostname = GetHostnameForClient(boost::asio::ip::address_v4(request->GetClient()->ip)); - std::string response = Trinity::StringFormat("{}:{}", hostname, sConfigMgr->GetIntDefault("BattlenetPort", 1119)); - - soap_response(request->GetClient(), SOAP_FILE); - soap_send_raw(request->GetClient(), response.c_str(), response.length()); - return soap_end_send(request->GetClient()); + context.response.set(boost::beast::http::field::content_type, "text/plain"); + context.response.body() = Trinity::StringFormat("{}:{}", GetHostnameForClient(session->GetRemoteIpAddress()), sConfigMgr->GetIntDefault("BattlenetPort", 1119)); + return RequestHandlerResult::Handled; } -int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request) +LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) { - char* buf = nullptr; - size_t len = 0; - soap_http_body(request->GetClient(), &buf, &len); - - Battlenet::JSON::Login::LoginForm loginForm; - if (!buf || !JSON::Deserialize(buf, &loginForm)) + JSON::Login::LoginForm loginForm; + if (!::JSON::Deserialize(context.request.body(), &loginForm)) { - ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = 400; - - Battlenet::JSON::Login::LoginResult loginResult; - loginResult.set_authentication_state(Battlenet::JSON::Login::LOGIN); + JSON::Login::LoginResult loginResult; + loginResult.set_authentication_state(JSON::Login::LOGIN); loginResult.set_error_code("UNABLE_TO_DECODE"); loginResult.set_error_message("There was an internal error while connecting to Battle.net. Please try again later."); - return SendResponse(request->GetClient(), loginResult); + + context.response.result(boost::beast::http::status::bad_request); + context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8"); + context.response.body() = ::JSON::Serialize(loginResult); + session->SendResponse(context); + + return RequestHandlerResult::Handled; } std::string login; @@ -340,44 +259,32 @@ int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request) std::string sentPasswordHash = CalculateShaPassHash(login, password); - request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery(stmt) - .WithChainingPreparedCallback([request, login, sentPasswordHash, this](QueryCallback& callback, PreparedQueryResult result) + session->QueueQuery(LoginDatabase.AsyncQuery(stmt) + .WithChainingPreparedCallback([this, session, context = std::move(context), login = std::move(login), sentPasswordHash = std::move(sentPasswordHash)](QueryCallback& callback, PreparedQueryResult result) mutable { - if (result) + if (!result) { - Field* fields = result->Fetch(); - uint32 accountId = fields[0].GetUInt32(); - std::string pass_hash = fields[1].GetString(); - uint32 failedLogins = fields[2].GetUInt32(); - std::string loginTicket = fields[3].GetString(); - uint32 loginTicketExpiry = fields[4].GetUInt32(); - bool isBanned = fields[5].GetUInt64() != 0; - - if (sentPasswordHash == pass_hash) - { - if (loginTicket.empty() || loginTicketExpiry < time(nullptr)) - { - std::array<uint8, 20> ticket = Trinity::Crypto::GetRandomBytes<20>(); + JSON::Login::LoginResult loginResult; + loginResult.set_authentication_state(JSON::Login::DONE); + context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8"); + context.response.body() = ::JSON::Serialize(loginResult); + session->SendResponse(context); + return; + } - loginTicket = "TC-" + ByteArrayToHexStr(ticket); - } + Field* fields = result->Fetch(); + uint32 accountId = fields[0].GetUInt32(); + std::string pass_hash = fields[1].GetString(); + uint32 failedLogins = fields[2].GetUInt32(); + std::string loginTicket = fields[3].GetString(); + uint32 loginTicketExpiry = fields[4].GetUInt32(); + bool isBanned = fields[5].GetUInt64() != 0; - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_AUTHENTICATION); - stmt->setString(0, loginTicket); - stmt->setUInt32(1, time(nullptr) + _loginTicketDuration); - stmt->setUInt32(2, accountId); - callback.WithPreparedCallback([request, loginTicket](PreparedQueryResult) - { - Battlenet::JSON::Login::LoginResult loginResult; - loginResult.set_authentication_state(Battlenet::JSON::Login::DONE); - loginResult.set_login_ticket(loginTicket); - sLoginService.SendResponse(request->GetClient(), loginResult); - }).SetNextQuery(LoginDatabase.AsyncQuery(stmt)); - return; - } - else if (!isBanned) + if (sentPasswordHash != pass_hash) + { + if (!isBanned) { - std::string ip_address = boost::asio::ip::address_v4(request->GetClient()->ip).to_string(); + std::string ip_address = session->GetRemoteIpAddress().to_string(); uint32 maxWrongPassword = uint32(sConfigMgr->GetIntDefault("WrongPass.MaxCount", 0)); if (sConfigMgr->GetBoolDefault("WrongPass.Logging", false)) @@ -421,31 +328,50 @@ int32 LoginRESTService::HandlePostLogin(std::shared_ptr<AsyncRequest> request) LoginDatabase.CommitTransaction(trans); } } + + JSON::Login::LoginResult loginResult; + loginResult.set_authentication_state(JSON::Login::DONE); + + context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8"); + context.response.body() = ::JSON::Serialize(loginResult); + session->SendResponse(context); + return; } - Battlenet::JSON::Login::LoginResult loginResult; - loginResult.set_authentication_state(Battlenet::JSON::Login::DONE); - sLoginService.SendResponse(request->GetClient(), loginResult); - }))); + if (loginTicket.empty() || loginTicketExpiry < time(nullptr)) + loginTicket = "TC-" + ByteArrayToHexStr(Trinity::Crypto::GetRandomBytes<20>()); - Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); }); + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_AUTHENTICATION); + stmt->setString(0, loginTicket); + stmt->setUInt32(1, time(nullptr) + _loginTicketDuration); + stmt->setUInt32(2, accountId); + callback.WithPreparedCallback([session, context = std::move(context), loginTicket = std::move(loginTicket)](PreparedQueryResult) mutable + { + JSON::Login::LoginResult loginResult; + loginResult.set_authentication_state(JSON::Login::DONE); + loginResult.set_login_ticket(loginTicket); + + context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8"); + context.response.body() = ::JSON::Serialize(loginResult); + session->SendResponse(context); + }).SetNextQuery(LoginDatabase.AsyncQuery(stmt)); + })); - return SOAP_OK; + return RequestHandlerResult::Async; } -int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncRequest> request) +LoginRESTService::RequestHandlerResult LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context) { - if (!request->GetClient()->userid) - return 401; - - request->SetCallback(std::make_unique<QueryCallback>(LoginDatabase.AsyncQuery([&] { - LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION); - stmt->setString(0, request->GetClient()->userid); - return stmt; - }()) - .WithPreparedCallback([this, request](PreparedQueryResult result) + std::string ticket = ExtractAuthorization(context.request); + if (ticket.empty()) + return HandleUnauthorized(std::move(session), context); + + LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION); + stmt->setString(0, ticket); + session->QueueQuery(LoginDatabase.AsyncQuery(stmt) + .WithPreparedCallback([this, session, context = std::move(context), ticket = std::move(ticket)](PreparedQueryResult result) mutable { - Battlenet::JSON::Login::LoginRefreshResult loginRefreshResult; + JSON::Login::LoginRefreshResult loginRefreshResult; if (result) { uint32 loginTicketExpiry = (*result)[0].GetUInt32(); @@ -456,7 +382,7 @@ int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncReques LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_EXISTING_AUTHENTICATION); stmt->setUInt32(0, uint32(now + _loginTicketDuration)); - stmt->setString(1, request->GetClient()->userid); + stmt->setString(1, ticket); LoginDatabase.Execute(stmt); } else @@ -465,34 +391,12 @@ int32 LoginRESTService::HandlePostRefreshLoginTicket(std::shared_ptr<AsyncReques else loginRefreshResult.set_is_expired(true); - SendResponse(request->GetClient(), loginRefreshResult); - }))); - - Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); }); - - return SOAP_OK; -} - -int32 LoginRESTService::SendResponse(soap* soapClient, google::protobuf::Message const& response) -{ - std::string jsonResponse = JSON::Serialize(response); - - soap_response(soapClient, SOAP_FILE); - soap_send_raw(soapClient, jsonResponse.c_str(), jsonResponse.length()); - return soap_end_send(soapClient); -} + context.response.set(boost::beast::http::field::content_type, "application/json;charset=utf-8"); + context.response.body() = ::JSON::Serialize(loginRefreshResult); + session->SendResponse(context); + })); -void LoginRESTService::HandleAsyncRequest(std::shared_ptr<AsyncRequest> request) -{ - if (!request->InvokeIfReady()) - { - Trinity::Asio::post(*_ioContext, [this, request]() { HandleAsyncRequest(request); }); - } - else if (request->GetResponseStatus()) - { - ResponseCodePlugin::GetForClient(request->GetClient())->ErrorCode = request->GetResponseStatus(); - SendResponse(request->GetClient(), Battlenet::JSON::Login::ErrorResponse()); - } + return RequestHandlerResult::Async; } std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std::string const& password) @@ -510,85 +414,8 @@ std::string LoginRESTService::CalculateShaPassHash(std::string const& name, std: return ByteArrayToHexStr(sha.GetDigest(), true); } -Namespace namespaces[] = -{ - { nullptr, nullptr, nullptr, nullptr } -}; - -LoginRESTService& LoginRESTService::Instance() -{ - static LoginRESTService instance; - return instance; -} - -char const* const LoginRESTService::ResponseCodePlugin::PluginId = "bnet-error-code"; - -int32 LoginRESTService::ResponseCodePlugin::Init(soap* s, soap_plugin* p, void* /*arg*/) -{ - ResponseCodePlugin* data = new ResponseCodePlugin(); - data->fresponse = s->fresponse; - - p->id = PluginId; - p->fcopy = &Copy; - p->fdelete = &Destroy; - p->data = data; - - s->fresponse = &ChangeResponse; - return SOAP_OK; -} - -int32 LoginRESTService::ResponseCodePlugin::Copy(soap* /*s*/, soap_plugin* dst, soap_plugin* src) -{ - dst->data = new ResponseCodePlugin(*reinterpret_cast<ResponseCodePlugin*>(src->data)); - return SOAP_OK; -} - -void LoginRESTService::ResponseCodePlugin::Destroy(soap* s, soap_plugin* p) -{ - ResponseCodePlugin* data = reinterpret_cast<ResponseCodePlugin*>(p->data); - s->fresponse = data->fresponse; - delete data; -} - -int32 LoginRESTService::ResponseCodePlugin::ChangeResponse(soap* s, int32 originalResponse, uint64 contentLength) -{ - ResponseCodePlugin* self = reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId)); - return self->fresponse(s, self->ErrorCode && originalResponse == SOAP_FILE ? self->ErrorCode : originalResponse, contentLength); -} - -LoginRESTService::ResponseCodePlugin* LoginRESTService::ResponseCodePlugin::GetForClient(soap* s) -{ - return ASSERT_NOTNULL(reinterpret_cast<ResponseCodePlugin*>(soap_lookup_plugin(s, PluginId))); -} - -char const* const LoginRESTService::ContentTypePlugin::PluginId = "bnet-content-type"; - -int32 LoginRESTService::ContentTypePlugin::Init(soap* s, soap_plugin* p, void* arg) -{ - ContentTypePlugin* data = new ContentTypePlugin(); - data->fposthdr = s->fposthdr; - data->ContentType = reinterpret_cast<char const*>(arg); - - p->id = PluginId; - p->fdelete = &Destroy; - p->data = data; - - s->fposthdr = &OnSetHeader; - return SOAP_OK; -} - -void LoginRESTService::ContentTypePlugin::Destroy(soap* s, soap_plugin* p) +void LoginRESTService::OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex) { - ContentTypePlugin* data = reinterpret_cast<ContentTypePlugin*>(p->data); - s->fposthdr = data->fposthdr; - delete data; + sLoginService.OnSocketOpen(std::move(sock), threadIndex); } - -int32 LoginRESTService::ContentTypePlugin::OnSetHeader(soap* s, char const* key, char const* value) -{ - ContentTypePlugin* self = reinterpret_cast<ContentTypePlugin*>(soap_lookup_plugin(s, PluginId)); - if (key && !strcmp("Content-Type", key)) - value = self->ContentType; - - return self->fposthdr(s, key, value); } diff --git a/src/server/bnetserver/REST/LoginRESTService.h b/src/server/bnetserver/REST/LoginRESTService.h index f783fea243a..1313493e023 100644 --- a/src/server/bnetserver/REST/LoginRESTService.h +++ b/src/server/bnetserver/REST/LoginRESTService.h @@ -18,98 +18,59 @@ #ifndef LoginRESTService_h__ #define LoginRESTService_h__ -#include "Define.h" -#include "IoContext.h" +#include "HttpService.h" #include "Login.pb.h" -#include "Session.h" -#include <boost/asio/ip/tcp.hpp> -#include <atomic> -#include <thread> - -class AsyncRequest; -struct soap; -struct soap_plugin; +#include "LoginHttpSession.h" +namespace Battlenet +{ enum class BanMode { BAN_IP = 0, BAN_ACCOUNT = 1 }; -class LoginRESTService +class LoginRESTService : public Trinity::Net::Http::HttpService<LoginHttpSession> { public: - LoginRESTService() : _ioContext(nullptr), _stopped(false), _port(0), _loginTicketDuration(0) { } + using RequestHandlerResult = Trinity::Net::Http::RequestHandlerResult; + using HttpRequest = Trinity::Net::Http::Request; + using HttpResponse = Trinity::Net::Http::Response; + using HttpRequestContext = Trinity::Net::Http::RequestContext; + using HttpSessionState = Trinity::Net::Http::SessionState; + + LoginRESTService() : HttpService("login"), _port(0), _loginTicketDuration(0) { } static LoginRESTService& Instance(); - bool Start(Trinity::Asio::IoContext* ioContext); - void Stop(); + bool StartNetwork(Trinity::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override; std::string const& GetHostnameForClient(boost::asio::ip::address const& address) const; - int32 GetPort() const { return _port; } + uint16 GetPort() const { return _port; } private: - void Run(); - - friend int32 handle_get_plugin(soap* soapClient); - friend int32 handle_post_plugin(soap* soapClient); - - using HttpMethodHandlerMap = std::unordered_map<std::string, int32(LoginRESTService::*)(std::shared_ptr<AsyncRequest>)>; - int32 HandleHttpRequest(soap* soapClient, char const* method, HttpMethodHandlerMap const& handlers); - - int32 HandleGetForm(std::shared_ptr<AsyncRequest> request); - int32 HandleGetGameAccounts(std::shared_ptr<AsyncRequest> request); - int32 HandleGetPortal(std::shared_ptr<AsyncRequest> request); + static void OnSocketAccept(boost::asio::ip::tcp::socket&& sock, uint32 threadIndex); - int32 HandlePostLogin(std::shared_ptr<AsyncRequest> request); - int32 HandlePostRefreshLoginTicket(std::shared_ptr<AsyncRequest> request); + static std::string ExtractAuthorization(HttpRequest const& request); - int32 SendResponse(soap* soapClient, google::protobuf::Message const& response); + RequestHandlerResult HandleGetForm(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context); + RequestHandlerResult HandleGetGameAccounts(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context); + RequestHandlerResult HandleGetPortal(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context); - void HandleAsyncRequest(std::shared_ptr<AsyncRequest> request); + RequestHandlerResult HandlePostLogin(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context); + RequestHandlerResult HandlePostRefreshLoginTicket(std::shared_ptr<LoginHttpSession> session, HttpRequestContext& context); - std::string CalculateShaPassHash(std::string const& name, std::string const& password); + static std::string CalculateShaPassHash(std::string const& name, std::string const& password); - struct ResponseCodePlugin - { - static char const* const PluginId; - static int32 Init(soap* s, soap_plugin*, void*); - static int32 Copy(soap* s, soap_plugin* dst, soap_plugin* src); - static void Destroy(soap* s, soap_plugin* p); - static int32 ChangeResponse(soap* s, int32 originalResponse, uint64 contentLength); - - static ResponseCodePlugin* GetForClient(soap* s); - - int32(*fresponse)(soap* s, int32 status, uint64 length); - int32 ErrorCode; - }; - - struct ContentTypePlugin - { - static char const* const PluginId; - static int32 Init(soap* s, soap_plugin* p, void*); - static void Destroy(soap* s, soap_plugin* p); - static int32 OnSetHeader(soap* s, char const* key, char const* value); - - int32(*fposthdr)(soap* s, char const* key, char const* value); - char const* ContentType; - }; - - Trinity::Asio::IoContext* _ioContext; - std::thread _thread; - std::atomic<bool> _stopped; - Battlenet::JSON::Login::FormInputs _formInputs; + JSON::Login::FormInputs _formInputs; std::string _bindIP; - int32 _port; + uint16 _port; std::array<std::string, 2> _hostnames; std::array<boost::asio::ip::address, 2> _addresses; uint32 _loginTicketDuration; - - HttpMethodHandlerMap _getHandlers; - HttpMethodHandlerMap _postHandlers; }; +} -#define sLoginService LoginRESTService::Instance() +#define sLoginService Battlenet::LoginRESTService::Instance() #endif // LoginRESTService_h__ diff --git a/src/server/bnetserver/Server/Session.cpp b/src/server/bnetserver/Server/Session.cpp index ea5256ed635..b450df14e1d 100644 --- a/src/server/bnetserver/Server/Session.cpp +++ b/src/server/bnetserver/Server/Session.cpp @@ -30,6 +30,7 @@ #include "RealmList.h" #include "RealmList.pb.h" #include "ServiceDispatcher.h" +#include "SslContext.h" #include "Timezone.h" #include <rapidjson/document.h> #include <zlib.h> @@ -73,7 +74,8 @@ void Battlenet::Session::GameAccountInfo::LoadResult(Field const* fields) DisplayName = Name; } -Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket)), _accountInfo(new AccountInfo()), _gameAccountInfo(nullptr), _locale(), +Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket), SslContext::instance()), + _accountInfo(new AccountInfo()), _gameAccountInfo(nullptr), _locale(), _os(), _build(0), _timezoneOffset(0min), _ipCountry(), _clientSecret(), _authed(false), _requestToken(0) { _headerLengthBuffer.Resize(2); diff --git a/src/server/bnetserver/Server/Session.h b/src/server/bnetserver/Server/Session.h index 6d22c202615..33a058f940c 100644 --- a/src/server/bnetserver/Server/Session.h +++ b/src/server/bnetserver/Server/Session.h @@ -20,11 +20,10 @@ #include "AsyncCallbackProcessor.h" #include "Duration.h" +#include "QueryResult.h" #include "Realm.h" -#include "SslContext.h" -#include "SslSocket.h" #include "Socket.h" -#include "QueryResult.h" +#include "SslSocket.h" #include <boost/asio/ip/tcp.hpp> #include <google/protobuf/message.h> #include <memory> @@ -65,9 +64,9 @@ using namespace bgs::protocol; namespace Battlenet { - class Session : public Socket<Session, SslSocket<SslContext>> + class Session : public Socket<Session, SslSocket<>> { - typedef Socket<Session, SslSocket<SslContext>> BattlenetSocket; + typedef Socket<Session, SslSocket<>> BattlenetSocket; public: struct LastPlayedCharacterInfo diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp index 6e9f8a8df1a..9cc4f755d9d 100644 --- a/src/server/database/Database/DatabaseWorkerPool.cpp +++ b/src/server/database/Database/DatabaseWorkerPool.cpp @@ -35,6 +35,7 @@ #include "MySQLWorkaround.h" #include <boost/asio/use_future.hpp> #include <mysqld_error.h> +#include <utility> #ifdef TRINITY_DEBUG #include <sstream> #include <boost/stacktrace.hpp> diff --git a/src/server/shared/CMakeLists.txt b/src/server/shared/CMakeLists.txt index 66f4ff56fd2..040381b946f 100644 --- a/src/server/shared/CMakeLists.txt +++ b/src/server/shared/CMakeLists.txt @@ -45,8 +45,7 @@ target_link_libraries(shared database rapidjson proto - zlib - gsoap) + zlib) set_target_properties(shared PROPERTIES diff --git a/src/server/shared/Networking/Http/BaseHttpSocket.cpp b/src/server/shared/Networking/Http/BaseHttpSocket.cpp new file mode 100644 index 00000000000..ca92c442aa2 --- /dev/null +++ b/src/server/shared/Networking/Http/BaseHttpSocket.cpp @@ -0,0 +1,115 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "BaseHttpSocket.h" +#include <boost/beast/http/serializer.hpp> + +namespace Trinity::Net::Http +{ +using RequestSerializer = boost::beast::http::request_serializer<ResponseBody>; +using ResponseSerializer = boost::beast::http::response_serializer<ResponseBody>; + +bool AbstractSocket::ParseRequest(MessageBuffer& packet, RequestParser& parser) +{ + if (!parser.is_done()) + { + // need more data in the payload + boost::system::error_code ec = {}; + std::size_t readDataSize = parser.put(boost::asio::const_buffer(packet.GetReadPointer(), packet.GetActiveSize()), ec); + packet.ReadCompleted(readDataSize); + } + + return parser.is_done(); +} + +std::string AbstractSocket::SerializeRequest(Request const& request) +{ + RequestSerializer serializer(request); + + std::string buffer; + while (!serializer.is_done()) + { + size_t totalBytes = 0; + boost::system::error_code ec = {}; + serializer.next(ec, [&]<typename ConstBufferSequence>(boost::system::error_code const&, ConstBufferSequence const& buffers) + { + size_t totalBytesInBuffers = boost::asio::buffer_size(buffers); + + buffer.reserve(buffer.size() + totalBytes); + + auto begin = boost::asio::buffers_begin(buffers); + auto end = boost::asio::buffers_end(buffers); + + std::copy(begin, end, std::back_inserter(buffer)); + totalBytes += totalBytesInBuffers; + }); + + serializer.consume(totalBytes); + } + + return buffer; +} + +MessageBuffer AbstractSocket::SerializeResponse(Request const& request, Response& response) +{ + response.prepare_payload(); + + ResponseSerializer serializer(response); + bool (*serializerIsDone)(ResponseSerializer&); + if (request.method() != boost::beast::http::verb::head) + { + serializerIsDone = [](ResponseSerializer& s) { return s.is_done(); }; + } + else + { + serializerIsDone = [](ResponseSerializer& s) { return s.is_header_done(); }; + serializer.split(true); + } + + MessageBuffer buffer; + while (!serializerIsDone(serializer)) + { + serializer.limit(buffer.GetRemainingSpace()); + + size_t totalBytes = 0; + boost::system::error_code ec = {}; + serializer.next(ec, [&]<typename ConstBufferSequence>(boost::system::error_code& currentError, ConstBufferSequence const& buffers) + { + size_t totalBytesInBuffers = boost::asio::buffer_size(buffers); + if (totalBytesInBuffers > buffer.GetRemainingSpace()) + { + currentError = boost::beast::http::error::need_more; + return; + } + + auto begin = boost::asio::buffers_begin(buffers); + auto end = boost::asio::buffers_end(buffers); + + std::copy(begin, end, buffer.GetWritePointer()); + buffer.WriteCompleted(totalBytesInBuffers); + totalBytes += totalBytesInBuffers; + }); + + serializer.consume(totalBytes); + + if (ec == boost::beast::http::error::need_more) + buffer.Resize(buffer.GetBufferSize() + 4096); + } + + return buffer; +} +} diff --git a/src/server/shared/Networking/Http/BaseHttpSocket.h b/src/server/shared/Networking/Http/BaseHttpSocket.h new file mode 100644 index 00000000000..330287252d2 --- /dev/null +++ b/src/server/shared/Networking/Http/BaseHttpSocket.h @@ -0,0 +1,191 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_BASE_HTTP_SOCKET_H +#define TRINITYCORE_BASE_HTTP_SOCKET_H + +#include "AsyncCallbackProcessor.h" +#include "DatabaseEnvFwd.h" +#include "HttpCommon.h" +#include "HttpSessionState.h" +#include "Optional.h" +#include "QueryCallback.h" +#include "Socket.h" +#include <boost/asio/buffers_iterator.hpp> +#include <boost/beast/http/parser.hpp> +#include <boost/beast/http/string_body.hpp> +#include <boost/uuid/uuid_io.hpp> + +namespace Trinity::Net::Http +{ +using RequestParser = boost::beast::http::request_parser<RequestBody>; + +class TC_SHARED_API AbstractSocket +{ +public: + AbstractSocket() = default; + AbstractSocket(AbstractSocket const& other) = default; + AbstractSocket(AbstractSocket&& other) = default; + AbstractSocket& operator=(AbstractSocket const& other) = default; + AbstractSocket& operator=(AbstractSocket&& other) = default; + virtual ~AbstractSocket() = default; + + static bool ParseRequest(MessageBuffer& packet, RequestParser& parser); + + static std::string SerializeRequest(Request const& request); + static MessageBuffer SerializeResponse(Request const& request, Response& response); + + virtual void SendResponse(RequestContext& context) = 0; + + virtual void QueueQuery(QueryCallback&& queryCallback) = 0; + + virtual std::string GetClientInfo() const = 0; + + virtual Optional<boost::uuids::uuid> GetSessionId() const = 0; +}; + +template<typename Derived, typename Stream> +class BaseSocket : public ::Socket<Derived, Stream>, public AbstractSocket +{ + using Base = ::Socket<Derived, Stream>; + +public: + template<typename... Args> + explicit BaseSocket(boost::asio::ip::tcp::socket&& socket, Args&&... args) + : Base(std::move(socket), std::forward<Args>(args)...) { } + + BaseSocket(BaseSocket const& other) = delete; + BaseSocket(BaseSocket&& other) = delete; + BaseSocket& operator=(BaseSocket const& other) = delete; + BaseSocket& operator=(BaseSocket&& other) = delete; + + ~BaseSocket() = default; + + void ReadHandler() override + { + if (!this->IsOpen()) + return; + + MessageBuffer& packet = this->GetReadBuffer(); + while (packet.GetActiveSize() > 0) + { + if (!ParseRequest(packet, *_httpParser)) + { + // Couldn't receive the whole data this time. + break; + } + + if (!HandleMessage(_httpParser->get())) + { + this->CloseSocket(); + break; + } + + this->ResetHttpParser(); + } + + this->AsyncRead(); + } + + bool HandleMessage(Request& request) + { + RequestContext context { .request = std::move(request) }; + + if (!_state) + _state = this->ObtainSessionState(context); + + RequestHandlerResult status = this->RequestHandler(context); + + if (status != RequestHandlerResult::Async) + this->SendResponse(context); + + return status != RequestHandlerResult::Error; + } + + virtual RequestHandlerResult RequestHandler(RequestContext& context) = 0; + + void SendResponse(RequestContext& context) override + { + MessageBuffer buffer = SerializeResponse(context.request, context.response); + + TC_LOG_DEBUG("server.http", "{} Request {} {} done, status {}", this->GetClientInfo(), ToStdStringView(context.request.method_string()), + ToStdStringView(context.request.target()), context.response.result_int()); + if (sLog->ShouldLog("server.http", LOG_LEVEL_TRACE)) + { + sLog->OutMessage("server.http", LOG_LEVEL_TRACE, "{} Request: ", this->GetClientInfo(), + CanLogRequestContent(context) ? SerializeRequest(context.request) : "<REDACTED>"); + sLog->OutMessage("server.http", LOG_LEVEL_TRACE, "{} Response: ", this->GetClientInfo(), + CanLogResponseContent(context) ? std::string_view(reinterpret_cast<char const*>(buffer.GetBasePointer()), buffer.GetActiveSize()) : "<REDACTED>"); + } + + this->QueuePacket(std::move(buffer)); + + if (!context.response.keep_alive()) + this->DelayedCloseSocket(); + } + + void QueueQuery(QueryCallback&& queryCallback) override + { + this->_queryProcessor.AddCallback(std::move(queryCallback)); + } + + bool Update() override + { + if (!this->Base::Update()) + return false; + + this->_queryProcessor.ProcessReadyCallbacks(); + return true; + } + + std::string GetClientInfo() const override + { + std::string info; + info.reserve(500); + auto itr = StringFormatTo(std::back_inserter(info), "[{}:{}", this->GetRemoteIpAddress().to_string(), this->GetRemotePort()); + if (_state) + itr = StringFormatTo(itr, ", Session Id: {}", boost::uuids::to_string(_state->Id)); + + StringFormatTo(itr, "]"); + return info; + } + + Optional<boost::uuids::uuid> GetSessionId() const final + { + if (this->_state) + return this->_state->Id; + + return {}; + } + +protected: + void ResetHttpParser() + { + this->_httpParser.reset(); + this->_httpParser.emplace(); + this->_httpParser->eager(true); + } + + virtual std::shared_ptr<SessionState> ObtainSessionState(RequestContext& context) const = 0; + + QueryCallbackProcessor _queryProcessor; + Optional<RequestParser> _httpParser; + std::shared_ptr<SessionState> _state; +}; +} + +#endif // TRINITYCORE_BASE_HTTP_SOCKET_H diff --git a/src/server/shared/Networking/Http/HttpCommon.h b/src/server/shared/Networking/Http/HttpCommon.h new file mode 100644 index 00000000000..5f6ecb6c147 --- /dev/null +++ b/src/server/shared/Networking/Http/HttpCommon.h @@ -0,0 +1,55 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_HTTP_COMMON_H +#define TRINITYCORE_HTTP_COMMON_H + +#include "Define.h" +#include <boost/beast/http/message.hpp> +#include <boost/beast/http/string_body.hpp> + +namespace Trinity::Net::Http +{ +using RequestBody = boost::beast::http::string_body; +using ResponseBody = boost::beast::http::string_body; + +using Request = boost::beast::http::request<RequestBody>; +using Response = boost::beast::http::response<ResponseBody>; + +struct RequestContext +{ + Request request; + Response response; + struct RequestHandler const* handler = nullptr; +}; + +TC_SHARED_API bool CanLogRequestContent(RequestContext const& context); +TC_SHARED_API bool CanLogResponseContent(RequestContext const& context); + +inline std::string_view ToStdStringView(boost::beast::string_view bsw) +{ + return { bsw.data(), bsw.size() }; +} + +enum class RequestHandlerResult +{ + Handled, + Error, + Async, +}; +} +#endif // TRINITYCORE_HTTP_COMMON_H diff --git a/src/server/shared/Networking/Http/HttpService.cpp b/src/server/shared/Networking/Http/HttpService.cpp new file mode 100644 index 00000000000..8995b65612a --- /dev/null +++ b/src/server/shared/Networking/Http/HttpService.cpp @@ -0,0 +1,258 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "HttpService.h" +#include "BaseHttpSocket.h" +#include "CryptoRandom.h" +#include "Timezone.h" +#include <boost/beast/version.hpp> +#include <boost/uuid/string_generator.hpp> +#include <fmt/chrono.h> + +namespace Trinity::Net::Http +{ +bool CanLogRequestContent(RequestContext const& context) +{ + return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogRequestContent); +} + +bool CanLogResponseContent(RequestContext const& context) +{ + return !context.handler || !context.handler->Flags.HasFlag(RequestHandlerFlag::DoNotLogResponseContent); +} + +RequestHandlerResult DispatcherService::HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context) +{ + TC_LOG_DEBUG(_logger, "{} Starting request {} {}", session->GetClientInfo(), + ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target())); + + std::string_view path = [&] + { + std::string_view path = ToStdStringView(context.request.target()); + size_t queryIndex = path.find('?'); + if (queryIndex != std::string_view::npos) + path = path.substr(0, queryIndex); + return path; + }(); + + context.handler = [&]() -> HttpMethodHandlerMap::mapped_type const* + { + switch (context.request.method()) + { + case boost::beast::http::verb::get: + case boost::beast::http::verb::head: + { + auto itr = _getHandlers.find(path); + return itr != _getHandlers.end() ? &itr->second : nullptr; + } + case boost::beast::http::verb::post: + { + auto itr = _postHandlers.find(path); + return itr != _postHandlers.end() ? &itr->second : nullptr; + } + default: + break; + } + return nullptr; + }(); + + SystemTimePoint responseDate = SystemTimePoint::clock::now(); + context.response.set(boost::beast::http::field::date, StringFormat("{:%a, %d %b %Y %T GMT}", responseDate - Timezone::GetSystemZoneOffsetAt(responseDate))); + context.response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); + context.response.keep_alive(context.response.keep_alive()); + + if (!context.handler) + return HandlePathNotFound(std::move(session), context); + + return context.handler->Func(std::move(session), context); +} + +RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context) +{ + context.response.result(boost::beast::http::status::not_found); + return RequestHandlerResult::Handled; +} + +RequestHandlerResult DispatcherService::HandleUnauthorized(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context) +{ + context.response.result(boost::beast::http::status::unauthorized); + return RequestHandlerResult::Handled; +} + +void DispatcherService::RegisterHandler(boost::beast::http::verb method, std::string_view path, + std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler, + RequestHandlerFlag flags) +{ + HttpMethodHandlerMap& handlerMap = [&]() -> HttpMethodHandlerMap& + { + switch (method) + { + case boost::beast::http::verb::get: + return _getHandlers; + case boost::beast::http::verb::post: + return _postHandlers; + default: + { + std::string_view methodString = ToStdStringView(boost::beast::http::to_string(method)); + ABORT_MSG("Tried to register a handler for unsupported HTTP method " STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(methodString)); + } + } + }(); + + handlerMap[std::string(path)] = { .Func = std::move(handler), .Flags = flags }; + TC_LOG_INFO(_logger, "Registered new handler for {} {}", ToStdStringView(boost::beast::http::to_string(method)), path); +} + +void SessionService::InitAndStoreSessionState(std::shared_ptr<SessionState> state, boost::asio::ip::address const& address) +{ + state->RemoteAddress = address; + + // Generate session id + { + std::unique_lock lock{ _sessionsMutex }; + + while (state->Id.is_nil() || _sessions.contains(state->Id)) + std::copy_n(Trinity::Crypto::GetRandomBytes<16>().begin(), 16, state->Id.begin()); + + TC_LOG_DEBUG(_logger, "Client at {} created new session {}", address.to_string(), boost::uuids::to_string(state->Id)); + _sessions[state->Id] = std::move(state); + } +} + +void SessionService::Start(Asio::IoContext& ioContext) +{ + _inactiveSessionsKillTimer = std::make_unique<Asio::DeadlineTimer>(ioContext); + _inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1)); + _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err) + { + if (err) + return; + + KillInactiveSessions(); + }); +} + +void SessionService::Stop() +{ + _inactiveSessionsKillTimer = nullptr; + { + std::unique_lock lock{ _sessionsMutex }; + _sessions.clear(); + } + { + std::unique_lock lock{ _inactiveSessionsMutex }; + _inactiveSessions.clear(); + } +} + +std::shared_ptr<SessionState> SessionService::FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address) +{ + std::shared_ptr<SessionState> state; + + { + std::shared_lock lock{ _sessionsMutex }; + auto itr = _sessions.find(boost::uuids::string_generator()(id.begin(), id.end())); + if (itr == _sessions.end()) + { + TC_LOG_DEBUG(_logger, "Client at {} attempted to use a session {} that was expired", address.to_string(), id); + return nullptr; // no session + } + + state = itr->second; + } + + if (state->RemoteAddress != address) + { + TC_LOG_ERROR(_logger, "Client at {} attempted to use a session {} that was last accessed from {}, denied access", + address.to_string(), id, state->RemoteAddress.to_string()); + return nullptr; + } + + { + std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex }; + _inactiveSessions.erase(state->Id); + } + + return state; +} + +void SessionService::MarkSessionInactive(boost::uuids::uuid const& id) +{ + { + std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex }; + _inactiveSessions.insert(id); + } + + { + auto itr = _sessions.find(id); + if (itr != _sessions.end()) + { + itr->second->InactiveTimestamp = TimePoint::clock::now() + Minutes(5); + TC_LOG_TRACE(_logger, "Session {} marked as inactive", boost::uuids::to_string(id)); + } + } +} + +void SessionService::KillInactiveSessions() +{ + std::set<boost::uuids::uuid> inactiveSessions; + + { + std::unique_lock lock{ _inactiveSessionsMutex }; + std::swap(_inactiveSessions, inactiveSessions); + } + + { + TimePoint now = TimePoint::clock::now(); + std::size_t inactiveSessionsCount = inactiveSessions.size(); + + std::unique_lock lock{ _sessionsMutex }; + for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); ) + { + auto sessionItr = _sessions.find(*itr); + if (sessionItr == _sessions.end() || sessionItr->second->InactiveTimestamp < now) + { + _sessions.erase(sessionItr); + itr = inactiveSessions.erase(itr); + } + else + ++itr; + } + + TC_LOG_DEBUG(_logger, "Killed {} inactive sessions", inactiveSessionsCount - inactiveSessions.size()); + } + + { + // restore sessions not killed to inactive queue + std::unique_lock lock{ _inactiveSessionsMutex }; + for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); ) + { + auto node = inactiveSessions.extract(itr++); + _inactiveSessions.insert(std::move(node)); + } + } + + _inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1)); + _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err) + { + if (err) + return; + + KillInactiveSessions(); + }); +} +} diff --git a/src/server/shared/Networking/Http/HttpService.h b/src/server/shared/Networking/Http/HttpService.h new file mode 100644 index 00000000000..01c66146ae3 --- /dev/null +++ b/src/server/shared/Networking/Http/HttpService.h @@ -0,0 +1,188 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_HTTP_SERVICE_H +#define TRINITYCORE_HTTP_SERVICE_H + +#include "AsioHacksFwd.h" +#include "Concepts.h" +#include "Define.h" +#include "EnumFlag.h" +#include "HttpCommon.h" +#include "HttpSessionState.h" +#include "Optional.h" +#include "SocketMgr.h" +#include <boost/uuid/uuid.hpp> +#include <functional> +#include <map> +#include <set> +#include <shared_mutex> + +namespace Trinity::Net::Http +{ +class AbstractSocket; + +enum class RequestHandlerFlag +{ + None = 0x0, + DoNotLogRequestContent = 0x1, + DoNotLogResponseContent = 0x2, +}; + +DEFINE_ENUM_FLAG(RequestHandlerFlag); + +struct RequestHandler +{ + std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> Func; + EnumFlag<RequestHandlerFlag> Flags = RequestHandlerFlag::None; +}; + +class TC_SHARED_API DispatcherService +{ +public: + explicit DispatcherService(std::string_view loggerSuffix) : _logger("server.http.dispatcher.") + { + _logger.append(loggerSuffix); + } + + RequestHandlerResult HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context); + + RequestHandlerResult HandlePathNotFound(std::shared_ptr<AbstractSocket> session, RequestContext& context); + RequestHandlerResult HandleUnauthorized(std::shared_ptr<AbstractSocket> session, RequestContext& context); + +protected: + void RegisterHandler(boost::beast::http::verb method, std::string_view path, + std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler, + RequestHandlerFlag flags = RequestHandlerFlag::None); + +private: + using HttpMethodHandlerMap = std::map<std::string, RequestHandler, std::less<>>; + + HttpMethodHandlerMap _getHandlers; + HttpMethodHandlerMap _postHandlers; + + std::string _logger; +}; + +class TC_SHARED_API SessionService +{ +public: + explicit SessionService(std::string_view loggerSuffix) : _logger("server.http.session.") + { + _logger.append(loggerSuffix); + } + + void Start(Asio::IoContext& ioContext); + void Stop(); + + std::shared_ptr<SessionState> FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address); + void MarkSessionInactive(boost::uuids::uuid const& id); + +protected: + void InitAndStoreSessionState(std::shared_ptr<SessionState> state, boost::asio::ip::address const& address); + + void KillInactiveSessions(); + +private: + std::shared_mutex _sessionsMutex; + std::map<boost::uuids::uuid, std::shared_ptr<SessionState>> _sessions; + + std::mutex _inactiveSessionsMutex; + std::set<boost::uuids::uuid> _inactiveSessions; + std::unique_ptr<Asio::DeadlineTimer> _inactiveSessionsKillTimer; + + std::string _logger; +}; + +template<typename Callable, typename SessionImpl> +concept HttpRequestHandler = invocable_r<Callable, RequestHandlerResult, std::shared_ptr<SessionImpl>, RequestContext&>; + +template<typename SessionImpl> +class HttpService : public SocketMgr<SessionImpl>, public DispatcherService, public SessionService +{ +public: + HttpService(std::string_view loggerSuffix) : DispatcherService(loggerSuffix), SessionService(loggerSuffix), _ioContext(nullptr), _logger("server.http.") + { + _logger.append(loggerSuffix); + } + + bool StartNetwork(Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int32 threadCount = 1) override + { + if (!SocketMgr<SessionImpl>::StartNetwork(ioContext, bindIp, port, threadCount)) + return false; + + SessionService::Start(ioContext); + return true; + } + + void StopNetwork() override + { + SessionService::Stop(); + SocketMgr<SessionImpl>::StopNetwork(); + } + + // http handling + using DispatcherService::RegisterHandler; + + template<HttpRequestHandler<SessionImpl> Callable> + void RegisterHandler(boost::beast::http::verb method, std::string_view path, Callable handler, RequestHandlerFlag flags = RequestHandlerFlag::None) + { + this->DispatcherService::RegisterHandler(method, path, [handler = std::move(handler)](std::shared_ptr<AbstractSocket> session, RequestContext& context) -> RequestHandlerResult + { + return handler(std::static_pointer_cast<SessionImpl>(std::move(session)), context); + }, flags); + } + + // session tracking + virtual std::shared_ptr<SessionState> CreateNewSessionState(boost::asio::ip::address const& address) + { + std::shared_ptr<SessionState> state = std::make_shared<SessionState>(); + InitAndStoreSessionState(state, address); + return state; + } + +protected: + class Thread : public NetworkThread<SessionImpl> + { + protected: + void SocketRemoved(std::shared_ptr<SessionImpl> session) override + { + if (Optional<boost::uuids::uuid> id = session->GetSessionId()) + _service->MarkSessionInactive(*id); + } + + private: + friend HttpService; + + SessionService* _service; + }; + + NetworkThread<SessionImpl>* CreateThreads() const override + { + Thread* threads = new Thread[this->GetNetworkThreadCount()]; + for (int32 i = 0; i < this->GetNetworkThreadCount(); ++i) + threads[i]._service = const_cast<HttpService*>(this); + return threads; + } + +private: + Asio::IoContext* _ioContext; + std::string _logger; +}; +} + +#endif // TRINITYCORE_HTTP_SERVICE_H diff --git a/src/server/shared/Networking/Http/HttpSessionState.h b/src/server/shared/Networking/Http/HttpSessionState.h new file mode 100644 index 00000000000..3012a2efc65 --- /dev/null +++ b/src/server/shared/Networking/Http/HttpSessionState.h @@ -0,0 +1,35 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_HTTP_SESSION_STATE_H +#define TRINITYCORE_HTTP_SESSION_STATE_H + +#include "Duration.h" +#include <boost/asio/ip/address.hpp> +#include <boost/uuid/uuid.hpp> + +namespace Trinity::Net::Http +{ +struct SessionState +{ + boost::uuids::uuid Id = { }; + boost::asio::ip::address RemoteAddress; + TimePoint InactiveTimestamp = TimePoint::max(); +}; +} + +#endif // TRINITYCORE_HTTP_SESSION_STATE_H diff --git a/src/server/shared/Networking/Http/HttpSocket.h b/src/server/shared/Networking/Http/HttpSocket.h new file mode 100644 index 00000000000..2bd18efd565 --- /dev/null +++ b/src/server/shared/Networking/Http/HttpSocket.h @@ -0,0 +1,75 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_HTTP_SOCKET_H +#define TRINITYCORE_HTTP_SOCKET_H + +#include "BaseHttpSocket.h" +#include <boost/beast/core/tcp_stream.hpp> + +namespace Trinity::Net::Http +{ +namespace Impl +{ +class BoostBeastSocketWrapper : public boost::beast::tcp_stream +{ +public: + using boost::beast::tcp_stream::tcp_stream; + + void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError) + { + socket().shutdown(what, shutdownError); + } + + void close(boost::system::error_code& /*error*/) + { + boost::beast::tcp_stream::close(); + } + + boost::asio::ip::tcp::socket::endpoint_type remote_endpoint() const + { + return socket().remote_endpoint(); + } +}; +} + +template <typename Derived> +class Socket : public BaseSocket<Derived, Impl::BoostBeastSocketWrapper> +{ + using SocketBase = BaseSocket<Derived, Impl::BoostBeastSocketWrapper>; + +public: + explicit Socket(boost::asio::ip::tcp::socket&& socket) + : SocketBase(std::move(socket)) { } + + Socket(Socket const& other) = delete; + Socket(Socket&& other) = delete; + Socket& operator=(Socket const& other) = delete; + Socket& operator=(Socket&& other) = delete; + + ~Socket() = default; + + void Start() override + { + this->ResetHttpParser(); + + this->AsyncRead(); + } +}; +} + +#endif // TRINITYCORE_HTTP_SOCKET_H diff --git a/src/server/shared/Networking/Http/HttpSslSocket.h b/src/server/shared/Networking/Http/HttpSslSocket.h new file mode 100644 index 00000000000..cdb70645e05 --- /dev/null +++ b/src/server/shared/Networking/Http/HttpSslSocket.h @@ -0,0 +1,97 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_HTTP_SSL_SOCKET_H +#define TRINITYCORE_HTTP_SSL_SOCKET_H + +#include "BaseHttpSocket.h" +#include "SslSocket.h" +#include <boost/beast/core/stream_traits.hpp> +#include <boost/beast/core/tcp_stream.hpp> +#include <boost/beast/ssl/ssl_stream.hpp> + +namespace Trinity::Net::Http +{ +namespace Impl +{ +class BoostBeastSslSocketWrapper : public ::SslSocket<boost::beast::ssl_stream<boost::beast::tcp_stream>> +{ +public: + using SslSocket::SslSocket; + + void shutdown(boost::asio::socket_base::shutdown_type what, boost::system::error_code& shutdownError) + { + _sslSocket.shutdown(shutdownError); + boost::beast::get_lowest_layer(_sslSocket).socket().shutdown(what, shutdownError); + } + + void close(boost::system::error_code& /*error*/) + { + boost::beast::get_lowest_layer(_sslSocket).close(); + } + + boost::asio::ip::tcp::socket::endpoint_type remote_endpoint() const + { + return boost::beast::get_lowest_layer(_sslSocket).socket().remote_endpoint(); + } +}; +} + +template <typename Derived> +class SslSocket : public BaseSocket<Derived, Impl::BoostBeastSslSocketWrapper> +{ + using SocketBase = BaseSocket<Derived, Impl::BoostBeastSslSocketWrapper>; + +public: + explicit SslSocket(boost::asio::ip::tcp::socket&& socket, boost::asio::ssl::context& sslContext) + : SocketBase(std::move(socket), sslContext) { } + + SslSocket(SslSocket const& other) = delete; + SslSocket(SslSocket&& other) = delete; + SslSocket& operator=(SslSocket const& other) = delete; + SslSocket& operator=(SslSocket&& other) = delete; + + ~SslSocket() = default; + + void Start() override + { + this->AsyncHandshake(); + } + + void AsyncHandshake() + { + this->underlying_stream().async_handshake(boost::asio::ssl::stream_base::server, + [self = this->shared_from_this()](boost::system::error_code const& error) { self->HandshakeHandler(error); }); + } + + void HandshakeHandler(boost::system::error_code const& error) + { + if (error) + { + TC_LOG_ERROR("server.http.session.ssl", "{} SSL Handshake failed {}", this->GetClientInfo(), error.message()); + this->CloseSocket(); + return; + } + + this->ResetHttpParser(); + + this->AsyncRead(); + } +}; +} + +#endif // TRINITYCORE_HTTP_SSL_SOCKET_H diff --git a/src/server/shared/Networking/NetworkThread.h b/src/server/shared/Networking/NetworkThread.h index 69d62403249..0195c48b9fc 100644 --- a/src/server/shared/Networking/NetworkThread.h +++ b/src/server/shared/Networking/NetworkThread.h @@ -77,7 +77,7 @@ public: return _connections; } - virtual void AddSocket(std::shared_ptr<SocketType> sock) + void AddSocket(std::shared_ptr<SocketType> sock) { std::lock_guard<std::mutex> lock(_newSocketsLock); diff --git a/src/server/shared/Networking/Socket.h b/src/server/shared/Networking/Socket.h index a996ecb2cbe..511f94ed366 100644 --- a/src/server/shared/Networking/Socket.h +++ b/src/server/shared/Networking/Socket.h @@ -18,14 +18,13 @@ #ifndef __SOCKET_H__ #define __SOCKET_H__ -#include "MessageBuffer.h" #include "Log.h" +#include "MessageBuffer.h" +#include <boost/asio/ip/tcp.hpp> #include <atomic> -#include <queue> #include <memory> -#include <functional> +#include <queue> #include <type_traits> -#include <boost/asio/ip/tcp.hpp> #define READ_BLOCK_SIZE 4096 #ifdef BOOST_ASIO_HAS_IOCP @@ -63,12 +62,19 @@ template<class T, class Stream = boost::asio::ip::tcp::socket> class Socket : public std::enable_shared_from_this<T> { public: - explicit Socket(boost::asio::ip::tcp::socket&& socket) : _socket(std::move(socket)), _remoteAddress(_socket.remote_endpoint().address()), - _remotePort(_socket.remote_endpoint().port()), _readBuffer(), _closed(false), _closing(false), _isWritingAsync(false) + template<typename... Args> + explicit Socket(boost::asio::ip::tcp::socket&& socket, Args&&... args) : _socket(std::move(socket), std::forward<Args>(args)...), + _remoteAddress(_socket.remote_endpoint().address()), _remotePort(_socket.remote_endpoint().port()), + _closed(false), _closing(false), _isWritingAsync(false) { _readBuffer.Resize(READ_BLOCK_SIZE); } + Socket(Socket const& other) = delete; + Socket(Socket&& other) = delete; + Socket& operator=(Socket const& other) = delete; + Socket& operator=(Socket&& other) = delete; + virtual ~Socket() { _closed = true; diff --git a/src/server/shared/Networking/SslSocket.h b/src/server/shared/Networking/SslSocket.h index e00b1b6b65e..c19c8612edf 100644 --- a/src/server/shared/Networking/SslSocket.h +++ b/src/server/shared/Networking/SslSocket.h @@ -24,11 +24,11 @@ namespace boostssl = boost::asio::ssl; -template<class SslContext, class Stream = boostssl::stream<boost::asio::ip::tcp::socket>> +template<class Stream = boostssl::stream<boost::asio::ip::tcp::socket>> class SslSocket { public: - explicit SslSocket(boost::asio::ip::tcp::socket&& socket) : _sslSocket(std::move(socket), SslContext::instance()) + explicit SslSocket(boost::asio::ip::tcp::socket&& socket, boost::asio::ssl::context& sslContext) : _sslSocket(std::move(socket), sslContext) { _sslSocket.set_verify_mode(boostssl::verify_none); } diff --git a/src/server/worldserver/CMakeLists.txt b/src/server/worldserver/CMakeLists.txt index da3bd43c2d5..f6df3e524de 100644 --- a/src/server/worldserver/CMakeLists.txt +++ b/src/server/worldserver/CMakeLists.txt @@ -51,7 +51,8 @@ target_link_libraries(worldserver PUBLIC scripts game - readline) + readline + gsoap) CollectIncludeDirectories( ${CMAKE_CURRENT_SOURCE_DIR} |