diff --git a/Makefile.djgpp b/Makefile.djgpp index b82d070..de02ed2 100644 --- a/Makefile.djgpp +++ b/Makefile.djgpp @@ -58,7 +58,7 @@ ALL_LDLIBS := -lc # Source, Binaries, Dependencies -SRC := $(shell find $(SRCDIR) -type f -name '*.c' | grep -v '/linux/' | grep -v '/server/' | grep -v '/primes/' | grep -v '/font/' | grep -v '/retired/' | grep -v '/test/') +SRC := $(shell find $(SRCDIR) -type f -name '*.c' | grep -v '/linux/' | grep -v '/server/' | grep -v '/primes/' | grep -v '/font/' | grep -v '/precache/' | grep -v '/retired/' | grep -v '/test/') OBJ := $(patsubst $(SRCDIR)/%,$(OBJDIR)/%,$(SRC:.c=.o)) DEP := $(OBJ:.o=.d) BIN := $(BINDIR)/$(TARGET) diff --git a/build.sh b/build.sh index 53c0aea..f36a139 100755 --- a/build.sh +++ b/build.sh @@ -23,11 +23,10 @@ # See: https://github.com/andrewwutw/build-djgpp mkdir -p \ - bin/data \ obj/client/src/system \ obj/client/src/dos \ obj/client/src/gui \ - obj/client/src/embedded \ + obj/client/src/thirdparty/SHA256 \ obj/client/src/thirdparty/serial \ obj/shared/thirdparty/memwatch \ obj/shared/thirdparty/blowfish-api \ @@ -41,4 +40,4 @@ make -f Makefile.djgpp rm bin/client -cp client/data/* bin/data/. +cp precache/out/* bin/. diff --git a/client/src/system/os.h b/client/src/system/os.h index 48c0771..1128b76 100644 --- a/client/src/system/os.h +++ b/client/src/system/os.h @@ -60,7 +60,7 @@ void linuxOsStartup(void); #else #define BITS32 -#define PATH_SLASH '\\' +#define OS_PATH_SLASH '\\' // DOS includes. #include diff --git a/kanga.world/site/plugins/kangaworld-integration/api/config.php b/kanga.world/site/plugins/kangaworld-integration/api/config.php deleted file mode 100644 index 4331e4a..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/api/config.php +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/kanga.world/site/plugins/kangaworld-integration/api/test.php b/kanga.world/site/plugins/kangaworld-integration/api/test.php deleted file mode 100644 index ba0a14a..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/api/test.php +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/kanga.world/site/plugins/kangaworld-integration/api/user.php b/kanga.world/site/plugins/kangaworld-integration/api/user.php index 8046d7e..ec1f071 100644 --- a/kanga.world/site/plugins/kangaworld-integration/api/user.php +++ b/kanga.world/site/plugins/kangaworld-integration/api/user.php @@ -37,6 +37,7 @@ function kpApiUserCreate($first, $last, $username, $email, $password, &$response ]); // Save SQL attributes. // ***TODO*** + // ***TODO*** Intercept password change and user enable/disable to update the DB. // Return result. $response['result'] = 'true'; @@ -51,26 +52,4 @@ function kpApiUserCreate($first, $last, $username, $email, $password, &$response } } - -function kpApiUserLogin($username, $pass, &$response) { - try { - // Find user by name instead of email. - $user = kirby()->users()->filterBy('name', $username)->first(); - if ($user) { - // ***TODO*** Must be activated! - // Attempt to sign them in. - kirby()->auth()->login($user->email(), $pass); - // They don't need an actual Kirby session, so log them off. - kirby()->auth()->logout(); - // Report it. - $response['result'] = 'true'; - $response['reason'] = 'User logged in.'; - } else { - $response['reason'] = 'User name or password incorrect.'; - } - } catch(Exception $e) { - $response['reason'] = 'User name or password incorrect.'; - } -} - ?> diff --git a/kanga.world/site/plugins/kangaworld-integration/classes/KPunch.php b/kanga.world/site/plugins/kangaworld-integration/classes/KPunch.php deleted file mode 100644 index 70a7c02..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/classes/KPunch.php +++ /dev/null @@ -1,50 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } catch(PDOException $ex) { - $db = null; - } - - return $db; - } - - - public static function databaseQueryToJSON($db, $query, $options = null) { - - $result = null; - - if ($db) { - $statement = $db->prepare($query); - $statement->execute($options); - $resultset = array(); - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $resultset[] = $row; - } - $result = json_encode($resultset); - } - - return $result; - } - -} - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/classes/KwConfig.php b/kanga.world/site/plugins/kangaworld-integration/classes/KwConfig.php deleted file mode 100644 index e12c3af..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/classes/KwConfig.php +++ /dev/null @@ -1,90 +0,0 @@ - diff --git a/kanga.world/site/plugins/kangaworld-integration/classes/KwNumbers.php b/kanga.world/site/plugins/kangaworld-integration/classes/KwNumbers.php deleted file mode 100644 index b255bbe..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/classes/KwNumbers.php +++ /dev/null @@ -1,83 +0,0 @@ - diff --git a/kanga.world/site/plugins/kangaworld-integration/classes/KwStrings.php b/kanga.world/site/plugins/kangaworld-integration/classes/KwStrings.php deleted file mode 100644 index 9f938c7..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/classes/KwStrings.php +++ /dev/null @@ -1,90 +0,0 @@ - diff --git a/kanga.world/site/plugins/kangaworld-integration/dialogs/create.php b/kanga.world/site/plugins/kangaworld-integration/dialogs/create.php deleted file mode 100644 index b7fbac7..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/dialogs/create.php +++ /dev/null @@ -1,22 +0,0 @@ - 'kwconfig/create', - 'load' => function () { - - return [ - 'component' => 'k-form-dialog', - 'props' => [ - 'fields' => require __DIR__ . '/fields.php', - 'submitButton' => t('create'), - ] - ]; - }, - 'submit' => function () { - return KwConfig::create(get()); - } -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/dialogs/delete.php b/kanga.world/site/plugins/kangaworld-integration/dialogs/delete.php deleted file mode 100644 index c35e9a8..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/dialogs/delete.php +++ /dev/null @@ -1,20 +0,0 @@ - 'kwconfig/(:any)/delete', - 'load' => function (string $id) { - return [ - 'component' => 'k-remove-dialog', - 'props' => [ - 'text' => 'Do you really want to delete this entry?' - ] - ]; - }, - 'submit' => function (string $id) { - return KwConfig::delete($id); - } -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/dialogs/fields.php b/kanga.world/site/plugins/kangaworld-integration/dialogs/fields.php deleted file mode 100644 index 303712b..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/dialogs/fields.php +++ /dev/null @@ -1,31 +0,0 @@ - [ - 'label' => 'Name', - 'type' => 'text' - ], - 'type' => [ - 'label' => 'Type', - 'type' => 'select', - 'empty' => false, - 'width' => '1/2', - 'options' => A::map(KwConfig::types(), function ($type) { - return ['value' => $type, 'text' => $type]; - }) - ], - 'description' => [ - 'label' => 'Description', - 'type' => 'textarea', - 'buttons' => false - ], - 'data' => [ - 'label' => 'Value', - 'type' => 'text' - ] -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/dialogs/update.php b/kanga.world/site/plugins/kangaworld-integration/dialogs/update.php deleted file mode 100644 index 40bcff7..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/dialogs/update.php +++ /dev/null @@ -1,23 +0,0 @@ - 'kwconfig/(:any)/update', - 'load' => function (string $id) { - $kwconfig = KwConfig::find($id); - - return [ - 'component' => 'k-form-dialog', - 'props' => [ - 'fields' => require __DIR__ . '/fields.php', - 'value' => $kwconfig - ] - ]; - }, - 'submit' => function (string $id) { - return KwConfig::update(get()); - } -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/dropdowns/kwconfig.php b/kanga.world/site/plugins/kangaworld-integration/dropdowns/kwconfig.php deleted file mode 100644 index 3c62053..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/dropdowns/kwconfig.php +++ /dev/null @@ -1,21 +0,0 @@ - 'kwconfig/(:any)', - 'action' => function (string $id) { - return [ - [ - 'text' => 'Edit', - 'icon' => 'edit', - 'dialog' => 'kwconfig/' . $id . '/update' - ], - [ - 'text' => 'Delete', - 'icon' => 'trash', - 'dialog' => 'kwconfig/' . $id . '/delete' - ] - ]; - } -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/features/api.php b/kanga.world/site/plugins/kangaworld-integration/features/api.php index df2949a..a564e80 100644 --- a/kanga.world/site/plugins/kangaworld-integration/features/api.php +++ b/kanga.world/site/plugins/kangaworld-integration/features/api.php @@ -1,7 +1,5 @@ [ - 'label' => 'Kanga World Configuration', - 'icon' => 'globe', - 'menu' => true, - // update and delete dialogs - 'dialogs' => [ - require __DIR__ . '/../dialogs/create.php', - require __DIR__ . '/../dialogs/update.php', - require __DIR__ . '/../dialogs/delete.php' - ], - // dropdown with edit and delete buttons - 'dropdowns' => [ - require __DIR__ . '/../dropdowns/kwconfig.php' - ], - // search for settings - 'searches' => [ - 'kwconfig' => require __DIR__ . '/../searches/kwconfig.php' - ], - // view route - 'views' => [ - require __DIR__ . '/../views/kwconfig.php', - require __DIR__ . '/../views/kwentry.php' - ], - ] - -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/features/collections.php b/kanga.world/site/plugins/kangaworld-integration/features/collections.php deleted file mode 100644 index fccbfa4..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/features/collections.php +++ /dev/null @@ -1,21 +0,0 @@ - function($site) { - return new Collection(KwConfig::list()); - }, - - 'kwnumbers' => function($site) { - return new Collection(KwNumbers::list()); - }, - - 'kwstrings' => function($site) { - return new Collection(KwStrings::list()); - } -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/features/options.php b/kanga.world/site/plugins/kangaworld-integration/features/options.php deleted file mode 100644 index 980ceb2..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/features/options.php +++ /dev/null @@ -1,13 +0,0 @@ - [ - 'host' => 'mysql', - 'port' => 3306, - 'data' => 'dosThing', - 'user' => 'dosThing', - 'pass' => 'password' - ] -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/index.css b/kanga.world/site/plugins/kangaworld-integration/index.css deleted file mode 100644 index b0d3252..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/index.css +++ /dev/null @@ -1,43 +0,0 @@ - -.k-kwconfig { - width: 100%; - table-layout: fixed; - border-spacing: 1px; -} -.k-kwconfig td, -.k-kwconfig th { - text-align: left; - font-size: var(--text-sm); - padding: var(--spacing-2); - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - background: var(--color-white); -} -.k-kwconfig th { - font-weight: var(--font-bold); -} -.k-kwconfig th button { - font: inherit; - text-align: left; - width: 100%; -} -.k-kwconfig-name { - /* width: 8rem; */ -} -.k-kwconfig-type { - /* width: 8rem; */ -} -.k-kwconfig-description { - width: 50%; -} -.k-kwconfig-data { - /* width: 10rem; */ - font-variant-numeric: tabular-nums; - text-align: right !important; -} -.k-kwconfig-options { - padding: 0 !important; - width: 3rem; - overflow: visible !important; -} diff --git a/kanga.world/site/plugins/kangaworld-integration/index.js b/kanga.world/site/plugins/kangaworld-integration/index.js deleted file mode 100644 index e171e7a..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/index.js +++ /dev/null @@ -1,145 +0,0 @@ -(function() { - "use strict"; - var render$1 = function() { - var _vm = this; - var _h = _vm.$createElement; - var _c = _vm._self._c || _h; - return _c("k-inside", [_c("k-view", [_c("k-header", [_vm._v(" Kanga World Configuration "), _c("k-button-group", { attrs: { "slot": "right" }, slot: "right" }, [_c("k-button", { attrs: { "text": "New Entry", "icon": "add" }, on: { "click": function($event) { - return _vm.$dialog("kwconfig/create"); - } } })], 1)], 1), _c("table", { staticClass: "k-kwconfig" }, [_c("tr", [_c("th", { staticClass: "k-kwconfig-name" }, [_c("button", { on: { "click": function($event) { - return _vm.sortBy("name"); - } } }, [_vm._v(" Name "), _vm.sort === "name" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-type" }, [_c("button", { on: { "click": function($event) { - return _vm.sortBy("type"); - } } }, [_vm._v(" Type "), _vm.sort === "type" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-description" }, [_c("button", { on: { "click": function($event) { - return _vm.sortBy("description"); - } } }, [_vm._v(" Description "), _vm.sort === "description" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-data" }, [_c("button", { on: { "click": function($event) { - return _vm.sortBy("data"); - } } }, [_vm._v(" Value "), _vm.sort === "data" ? _c("span", { domProps: { "innerHTML": _vm._s(_vm.sortArrow) } }) : _vm._e()])]), _c("th", { staticClass: "k-kwconfig-options" })]), _vm._l(_vm.kwconfig, function(kwconfig, name) { - return _c("tr", { key: name }, [_c("td", { staticClass: "k-kwconfig-name" }, [_vm._v(_vm._s(kwconfig.name))]), _c("td", { staticClass: "k-kwconfig-type" }, [_vm._v(_vm._s(kwconfig.type))]), _c("td", { staticClass: "k-kwconfig-description" }, [_vm._v(_vm._s(kwconfig.description))]), _c("td", { staticClass: "k-kwconfig-data" }, [_vm._v(_vm._s(kwconfig.data))]), _c("td", { staticClass: "k-kwconfig-options" }, [_c("k-options-dropdown", { attrs: { "options": "kwconfig/" + name } })], 1)]); - })], 2)], 1)], 1); - }; - var staticRenderFns$1 = []; - render$1._withStripped = true; - var KwConfig_vue_vue_type_style_index_0_lang = ""; - function normalizeComponent(scriptExports, render2, staticRenderFns2, functionalTemplate, injectStyles, scopeId, moduleIdentifier, shadowMode) { - var options = typeof scriptExports === "function" ? scriptExports.options : scriptExports; - if (render2) { - options.render = render2; - options.staticRenderFns = staticRenderFns2; - options._compiled = true; - } - if (functionalTemplate) { - options.functional = true; - } - if (scopeId) { - options._scopeId = "data-v-" + scopeId; - } - var hook; - if (moduleIdentifier) { - hook = function(context) { - context = context || this.$vnode && this.$vnode.ssrContext || this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; - if (!context && typeof __VUE_SSR_CONTEXT__ !== "undefined") { - context = __VUE_SSR_CONTEXT__; - } - if (injectStyles) { - injectStyles.call(this, context); - } - if (context && context._registeredComponents) { - context._registeredComponents.add(moduleIdentifier); - } - }; - options._ssrRegister = hook; - } else if (injectStyles) { - hook = shadowMode ? function() { - injectStyles.call(this, (options.functional ? this.parent : this).$root.$options.shadowRoot); - } : injectStyles; - } - if (hook) { - if (options.functional) { - options._injectStyles = hook; - var originalRender = options.render; - options.render = function renderWithStyleInjection(h, context) { - hook.call(context); - return originalRender(h, context); - }; - } else { - var existing = options.beforeCreate; - options.beforeCreate = existing ? [].concat(existing, hook) : [hook]; - } - } - return { - exports: scriptExports, - options - }; - } - const __vue2_script$1 = { - props: { - dir: String, - sort: String, - kwconfig: Object - }, - computed: { - sortArrow() { - return this.dir === "asc" ? "↓" : "↑"; - } - }, - methods: { - sortBy(sort) { - let dir = "asc"; - if (sort === this.sort) - dir = this.dir === "asc" ? "desc" : "asc"; - this.$reload({ - query: { - dir, - sort - } - }); - } - } - }; - const __cssModules$1 = {}; - var __component__$1 = /* @__PURE__ */ normalizeComponent(__vue2_script$1, render$1, staticRenderFns$1, false, __vue2_injectStyles$1, null, null, null); - function __vue2_injectStyles$1(context) { - for (let o in __cssModules$1) { - this[o] = __cssModules$1[o]; - } - } - __component__$1.options.__file = "src/components/KwConfig.vue"; - var KwConfig = /* @__PURE__ */ function() { - return __component__$1.exports; - }(); - var render = function() { - var _vm = this; - var _h = _vm.$createElement; - var _c = _vm._self._c || _h; - return _c("k-inside", [_c("k-view", [_c("k-header", [_vm._v(" " + _vm._s(_vm.kwentry.name) + " "), _c("k-button-group", { attrs: { "slot": "left" }, slot: "left" }, [_c("k-button", { attrs: { "text": "Edit", "icon": "edit" }, on: { "click": function($event) { - return _vm.$dialog("kwconfig/" + _vm.kwentry.name + "/update"); - } } }), _c("k-button", { attrs: { "text": "Delete", "icon": "trash" }, on: { "click": function($event) { - return _vm.$dialog("kwconfig/" + _vm.kwentry.name + "/delete"); - } } })], 1)], 1), _c("table", { staticClass: "k-kwconfig" }, [_c("tr", [_c("th", { staticClass: "k-kwconfig-type" }, [_vm._v("Type")]), _c("th", { staticClass: "k-kwconfig-description" }, [_vm._v("Description")]), _c("th", { staticClass: "k-kwconfig-data" }, [_vm._v("Value")])]), _c("tr", [_c("td", { staticClass: "k-kwconfig-type" }, [_vm._v(_vm._s(_vm.kwentry.type))]), _c("td", { staticClass: "k-kwconfig-description" }, [_vm._v(_vm._s(_vm.kwentry.description))]), _c("td", { staticClass: "k-kwconfig-data" }, [_vm._v(_vm._s(_vm.kwentry.data))])])])], 1)], 1); - }; - var staticRenderFns = []; - render._withStripped = true; - const __vue2_script = { - props: { - kwentry: Object - } - }; - const __cssModules = {}; - var __component__ = /* @__PURE__ */ normalizeComponent(__vue2_script, render, staticRenderFns, false, __vue2_injectStyles, null, null, null); - function __vue2_injectStyles(context) { - for (let o in __cssModules) { - this[o] = __cssModules[o]; - } - } - __component__.options.__file = "src/components/KwEntry.vue"; - var KwEntry = /* @__PURE__ */ function() { - return __component__.exports; - }(); - panel.plugin("kangaroopunch/kangaworld-integration", { - components: { - "k-kwconfig-view": KwConfig, - "k-kwentry-view": KwEntry - } - }); -})(); diff --git a/kanga.world/site/plugins/kangaworld-integration/index.php b/kanga.world/site/plugins/kangaworld-integration/index.php index 28aba97..dd216ad 100644 --- a/kanga.world/site/plugins/kangaworld-integration/index.php +++ b/kanga.world/site/plugins/kangaworld-integration/index.php @@ -1,19 +1,8 @@ 'KwConfig.php', - 'KangarooPunch\KwNumbers' => 'KwNumbers.php', - 'KangarooPunch\KwStrings' => 'KwStrings.php', - 'KangarooPunch\KPunch' => 'KPunch.php' -], __DIR__ . '/classes'); - - Kirby::plugin('kangaroopunch/kangaworld-integration', [ - 'api' => require __DIR__ . '/features/api.php', -// 'areas' => require __DIR__ . '/features/areas.php', - 'collections' => require __DIR__ . '/features/collections.php', - 'options' => require __DIR__ . '/features/options.php' + 'api' => require __DIR__ . '/features/api.php' ]); diff --git a/kanga.world/site/plugins/kangaworld-integration/package.json b/kanga.world/site/plugins/kangaworld-integration/package.json deleted file mode 100644 index a933ea2..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "dev": "npx -y kirbyup src/index.js --watch", - "build": "npx -y kirbyup src/index.js" - } -} diff --git a/kanga.world/site/plugins/kangaworld-integration/searches/kwconfig.php b/kanga.world/site/plugins/kangaworld-integration/searches/kwconfig.php deleted file mode 100644 index 2c979f2..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/searches/kwconfig.php +++ /dev/null @@ -1,32 +0,0 @@ - 'Configuration', - 'icon' => 'globe', - 'query' => function(string $query) { - - $kwconfig = KwConfig::list(); - $results = []; - - foreach ($kwconfig as $kwentry) { - if ((Str::contains($kwentry['name'], $query, true) === true) || - (Str::contains($kwentry['description'], $query, true) === true) || - (Str::contains($kwentry['data'], $query, true) === true)) { - $results[] = [ - 'text' => $kwentry['name'], - 'link' => '/kwconfig/' . esc($kwentry['name'], 'url'), - 'image' => [ - 'icon' => 'globe', - 'back' => 'purple-400' - ] - ]; - } - } - - return $results; - } -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/src/components/KwConfig.vue b/kanga.world/site/plugins/kangaworld-integration/src/components/KwConfig.vue deleted file mode 100644 index d21b7ce..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/src/components/KwConfig.vue +++ /dev/null @@ -1,126 +0,0 @@ - - - - - diff --git a/kanga.world/site/plugins/kangaworld-integration/src/components/KwEntry.vue b/kanga.world/site/plugins/kangaworld-integration/src/components/KwEntry.vue deleted file mode 100644 index ec4cbef..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/src/components/KwEntry.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - diff --git a/kanga.world/site/plugins/kangaworld-integration/src/index.js b/kanga.world/site/plugins/kangaworld-integration/src/index.js deleted file mode 100644 index 10b1283..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/src/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import KwConfig from "./components/KwConfig.vue"; -import KwEntry from "./components/KwEntry.vue"; - -panel.plugin("kangaroopunch/kangaworld-integration", { - components: { - "k-kwconfig-view": KwConfig, - "k-kwentry-view": KwEntry - } -}); diff --git a/kanga.world/site/plugins/kangaworld-integration/views/kwconfig.php b/kanga.world/site/plugins/kangaworld-integration/views/kwconfig.php deleted file mode 100644 index 594f3a7..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/views/kwconfig.php +++ /dev/null @@ -1,25 +0,0 @@ - 'kwconfig', - 'action' => function () { - $sort = get('sort', 'name'); - $dir = get('dir', 'asc'); - $kwconfig = KwConfig::list(); - $kwconfig = A::sort($kwconfig, $sort, $dir); - - return [ - 'component' => 'k-kwconfig-view', - 'props' => [ - 'dir' => $dir, - 'sort' => $sort, - 'kwconfig' => $kwconfig - ], - 'search' => 'kwconfig' - ]; - } -]; - -?> diff --git a/kanga.world/site/plugins/kangaworld-integration/views/kwentry.php b/kanga.world/site/plugins/kangaworld-integration/views/kwentry.php deleted file mode 100644 index dcaca07..0000000 --- a/kanga.world/site/plugins/kangaworld-integration/views/kwentry.php +++ /dev/null @@ -1,25 +0,0 @@ - 'kwconfig/(:any)', - 'action' => function ($id) { - $kwentry = KwConfig::find($id); - - return [ - 'component' => 'k-kwentry-view', - 'breadcrumb' => [ - [ - 'label' => $kwentry['name'], - 'link' => 'kwconfig/' . $id - ] - ], - 'props' => [ - 'kwentry' => $kwentry - ] - ]; - } -]; - -?> diff --git a/kpmpgsmkii.pro b/kpmpgsmkii.pro index 01169bb..e1aa58c 100644 --- a/kpmpgsmkii.pro +++ b/kpmpgsmkii.pro @@ -21,8 +21,8 @@ TEMPLATE = subdirs CONFIG *= ORDERED SUBDIRS = \ - client -# server +# client \ + server # precache # font # primes diff --git a/server/server.pro b/server/server.pro index 74ecb02..c86a751 100644 --- a/server/server.pro +++ b/server/server.pro @@ -50,6 +50,7 @@ HEADERS = \ $$SHARED/thirdparty/tiny-AES-c/aes.h \ $$SHARED/thirdparty/tiny-AES128-C/pkcs7_padding.h \ src/client.h \ + src/client/file.h \ src/client/login.h \ src/client/pong.h \ src/client/shutdown.h \ @@ -59,6 +60,7 @@ HEADERS = \ src/network.h \ src/os.h \ src/rest.h \ + src/database.h \ src/server.h SOURCES = \ @@ -72,6 +74,7 @@ SOURCES = \ $$SHARED/thirdparty/tiny-AES-c/aes.c \ $$SHARED/thirdparty/tiny-AES128-C/pkcs7_padding.c \ src/client.c \ + src/client/file.c \ src/client/login.c \ src/client/pong.c \ src/client/shutdown.c \ @@ -81,12 +84,14 @@ SOURCES = \ src/main.c \ src/network.c \ src/rest.c \ + src/database.c \ src/server.c LIBS = \ -L/usr/lib/x86_64-linux-gnu/ \ -ldl \ -lm \ + -lmariadb \ -lpthread \ -lgnutls \ -lcrypt \ diff --git a/server/src/client.c b/server/src/client.c index 66cf7c1..2cae1eb 100644 --- a/server/src/client.c +++ b/server/src/client.c @@ -24,13 +24,13 @@ #include "console.h" #include "packet.h" #include "server.h" -#include "rest.h" #include "client/login.h" #include "client/signup.h" #include "client/pong.h" #include "client/version.h" #include "client/shutdown.h" +#include "client/file.h" typedef void (*clientApi)(ClientThreadT *client, PacketDecodeDataT *data); @@ -40,7 +40,7 @@ typedef void (*clientApi)(ClientThreadT *client, PacketDecodeDataT *data); static clientApi _clientApiMethod[PACKET_TYPE_COUNT]; -static uint8_t clientDequeuePacket(ClientThreadT *client); +static uint8_t clientDequeuePacket(ClientThreadT *client); static uint8_t clientDequeuePacket(ClientThreadT *client) { @@ -105,6 +105,7 @@ void clientStartup(void) { memset(_clientApiMethod, 0, sizeof(_clientApiMethod)); _clientApiMethod[PACKET_TYPE_CLIENT_SHUTDOWN] = clientApiClientShutdown; + _clientApiMethod[PACKET_TYPE_FILE_REQUEST] = clientApiFileRequest; _clientApiMethod[PACKET_TYPE_LOGIN] = clientApiLogin; _clientApiMethod[PACKET_TYPE_PONG] = clientApiPong; _clientApiMethod[PACKET_TYPE_SIGNUP] = clientApiSignup; diff --git a/server/src/client.h b/server/src/client.h index d0434c0..df1dbe9 100644 --- a/server/src/client.h +++ b/server/src/client.h @@ -35,6 +35,7 @@ typedef struct ClientRawPacketS { } ClientRawPacketT; typedef struct ClientThreadS { + // System Stuff. uint64_t threadIndex; pthread_t threadHandle; pthread_attr_t threadAttributes; @@ -49,6 +50,8 @@ typedef struct ClientThreadS { double pingHigh; double pingLow; void *peer; + // User State Stuff. + uint8_t authenticated; } ClientThreadT; diff --git a/server/src/client/file.c b/server/src/client/file.c new file mode 100644 index 0000000..49890ac --- /dev/null +++ b/server/src/client/file.c @@ -0,0 +1,126 @@ +/* + * Kangaroo Punch MultiPlayer Game Server Mark II + * Copyright (C) 2020-2021 Scott Duensing + * + * 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 3 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 . + * + */ + + +#include "database.h" + +#include "file.h" + + +static void clientApiFileRequestCacheCheck(ClientThreadT *client, PacketDecodeDataT *data); +static void clientApiFileRequestClose(ClientThreadT *client, PacketDecodeDataT *data); +static void clientApiFileRequestOpen(ClientThreadT *client, PacketDecodeDataT *data); +static void clientApiFileRequestRead(ClientThreadT *client, PacketDecodeDataT *data); + + +void clientApiFileRequest(ClientThreadT *client, PacketDecodeDataT *data) { + FileRequestsT request = 0; + PacketEncodeDataT encoded = { 0 }; + char *packetData = NULL; + uint16_t length = 0; + + // Must be logged in to do file operations. + if (!client->authenticated) { + // Extract the request type. We get more data later. + packetContentUnpack(data->data, "i", &request); + + switch (request) { + case FILE_REQUEST_CACHE_CHECK: + clientApiFileRequestCacheCheck(client, data); + break; + + case FILE_REQUEST_OPEN: + clientApiFileRequestOpen(client, data); + break; + + case FILE_REQUEST_READ: + clientApiFileRequestRead(client, data); + break; + + case FILE_REQUEST_CLOSE: + clientApiFileRequestClose(client, data); + break; + + default: + // No idea what they want. First value is 0 for fail, 1 for success. + packetData = packetContentPack(&length, "i", 0); + // Build packet. + encoded.control = PACKET_CONTROL_DAT; + encoded.packetType = PACKET_TYPE_FILE_RESPONSE; + encoded.channel = data->channel; + encoded.encrypt = 0; + packetEncode(client->packetThreadData, &encoded, packetData, length); + // Send it. + packetSend(client->packetThreadData, &encoded); + DEL(packetData); + break; + } + + } // authenticated +} + + +static void clientApiFileRequestCacheCheck(ClientThreadT *client, PacketDecodeDataT *data) { + FileRequestsT request = 0; + char *path = NULL; + char *sha256 = NULL; + PacketEncodeDataT encoded = { 0 }; + char *packetData = NULL; + uint16_t length = 0; + + // Extract the request. + packetContentUnpack(data->data, "i", &request, &path, &sha256); + + // Look up the entry and compare SHA. + +} + + +static void clientApiFileRequestClose(ClientThreadT *client, PacketDecodeDataT *data) { + FileRequestsT request = 0; + PacketEncodeDataT encoded = { 0 }; + char *packetData = NULL; + uint16_t length = 0; + +} + + +static void clientApiFileRequestOpen(ClientThreadT *client, PacketDecodeDataT *data) { + FileRequestsT request = 0; + char *path = NULL; + char *sha256 = NULL; + PacketEncodeDataT encoded = { 0 }; + char *packetData = NULL; + uint16_t length = 0; + + // Extract the request. + packetContentUnpack(data->data, "i", &request, &path); + + // Look up the entry and fetch SHA. + +} + + +static void clientApiFileRequestRead(ClientThreadT *client, PacketDecodeDataT *data) { + FileRequestsT request = 0; + PacketEncodeDataT encoded = { 0 }; + char *packetData = NULL; + uint16_t length = 0; + +} diff --git a/server/src/client/file.h b/server/src/client/file.h new file mode 100644 index 0000000..1fe1d76 --- /dev/null +++ b/server/src/client/file.h @@ -0,0 +1,32 @@ +/* + * Kangaroo Punch MultiPlayer Game Server Mark II + * Copyright (C) 2020-2021 Scott Duensing + * + * 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 3 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 . + * + */ + + +#ifndef FILE_H +#define FILE_H + + +#include "os.h" +#include "client.h" + + +void clientApiFileRequest(ClientThreadT *client, PacketDecodeDataT *data); + + +#endif // FILE_H diff --git a/server/src/client/login.c b/server/src/client/login.c index 119683c..7b6dcca 100644 --- a/server/src/client/login.c +++ b/server/src/client/login.c @@ -18,7 +18,7 @@ */ -#include "rest.h" +#include "database.h" #include "login.h" @@ -28,31 +28,26 @@ void clientApiLogin(ClientThreadT *client, PacketDecodeDataT *data) { char *user = NULL; char *pass = NULL; PacketEncodeDataT encoded = { 0 }; - json_object *response = NULL; uint8_t result = 0; - char *string = NULL; char *buffer = NULL; uint16_t length = 0; - packetContentUnpack(data->data, "ss", &user, &pass); - response = restRequest("USER_LOGIN", "ss", - "user", user, - "pass", pass - ); - DEL(user); - DEL(pass); - if (response) { - string = (char *)json_object_get_string(json_object_object_get(response, "result")); - result = (string[0] == 't' || string[0] == 'T') ? 1 : 0; - buffer = (char *)json_object_get_string(json_object_object_get(response, "reason")); + // This only makes sense if they're not logged in. + if (!client->authenticated) { + packetContentUnpack(data->data, "ss", &user, &pass); + result = dbUserLogin(user, pass); + DEL(user); + DEL(pass); + } + if (result) { + buffer = "Logged in."; + client->authenticated = 1; } else { // Something bad happened. - result = 0; buffer = "Unknown error. Sorry."; } logWrite("Login: %d %s\r\n", result, buffer); packetData = packetContentPack(&length, "is", result, buffer); - if (response) restRelease(response); // Build packet. encoded.control = PACKET_CONTROL_DAT; encoded.packetType = PACKET_TYPE_LOGIN_RESULT; diff --git a/server/src/client/pong.c b/server/src/client/pong.c index ab15435..1a076bc 100644 --- a/server/src/client/pong.c +++ b/server/src/client/pong.c @@ -38,7 +38,6 @@ void clientApiPong(ClientThreadT *client, PacketDecodeDataT *data) { client->pingStats[client->pingHead] = d; client->pingHead++; if (client->pingHead >= PING_STATS_SIZE) client->pingHead = 0; - // ***TODO*** Probably need a mutex here. if (d > client->pingHigh) client->pingHigh = d; if (d < client->pingLow) client->pingLow = d; client->pingAverage = 0; diff --git a/server/src/client/signup.c b/server/src/client/signup.c index 957a080..81cfd81 100644 --- a/server/src/client/signup.c +++ b/server/src/client/signup.c @@ -19,6 +19,7 @@ #include "rest.h" +#include "database.h" #include "signup.h" @@ -37,23 +38,62 @@ void clientApiSignup(ClientThreadT *client, PacketDecodeDataT *data) { PacketEncodeDataT encoded = { 0 }; uint16_t length = 0; - packetContentUnpack(data->data, "sssss", &email, &first, &last, &pass, &user); - response = restRequest("USER_CREATE", "sssss", - "first", first, - "last", last, - "user", user, - "pass", pass, - "email", email - ); - if (response) { - string = (char *)json_object_get_string(json_object_object_get(response, "result")); - result = (string[0] == 't' || string[0] == 'T') ? 1 : 0; - buffer = (char *)json_object_get_string(json_object_object_get(response, "reason")); - } else { - // Something bad happened. - result = 0; - buffer = "Unknown error. Sorry."; + // This only makes sense if they're not logged in. + if (!client->authenticated) { + // Get info for the user. + packetContentUnpack(data->data, "sssss", &email, &first, &last, &pass, &user); + + // See if they already exist in the database. + if (dbUserNameExists(user)) { + // User name already exists. + result = 0; + buffer = "User name already exists."; + } else { + if (dbUserEmailExists(email)) { + // Email already exists. + result = 0; + buffer = "E-mail already exists."; + } else { + // Create user in DB. + if (dbUserCreate(first, last, user, pass, email) == FAIL) { + result = 0; + buffer = "Unknown error. Sorry."; + } else { + // If we found a REST endpoint, create the user there as well. + if (__restAvailable) { + response = restRequest("USER_CREATE", "sssss", + "first", first, + "last", last, + "user", user, + "pass", pass, + "email", email + ); + if (response) { + // Success with REST. + string = (char *)json_object_get_string(json_object_object_get(response, "result")); + result = (string[0] == 't' || string[0] == 'T') ? 1 : 0; + buffer = (char *)json_object_get_string(json_object_object_get(response, "reason")); + } else { + // Something bad happened. + result = 0; + buffer = "Unknown error. Sorry."; + } + } else { + // Success with DB only! + result = 1; + buffer = "Check your E-mail for activation instructions."; + } + } + } + } + + DEL(first); + DEL(last); + DEL(user); + DEL(pass); + DEL(email); } + packetData = packetContentPack(&length, "is", result, buffer); if (response) restRelease(response); // Build packet. diff --git a/server/src/client/version.c b/server/src/client/version.c index a9d3c04..d3b4118 100644 --- a/server/src/client/version.c +++ b/server/src/client/version.c @@ -18,7 +18,7 @@ */ -#include "rest.h" +#include "database.h" #include "console.h" #include "array.h" @@ -31,96 +31,88 @@ void clientApiVersionBad(ClientThreadT *client, PacketDecodeDataT *data) { void clientApiVersionOkay(ClientThreadT *client, PacketDecodeDataT *data) { - json_object *response = NULL; - RestStringMapT *strings = NULL; - RestIntegerMapT *integers = NULL; - uint64_t i = 0; uint64_t x = 0; uint32_t y = 0; uint16_t length = 0; char *buffer = NULL; PacketEncodeDataT encoded = { 0 }; + DBTableT *record = NULL; + DBTableT **table = NULL; (void)data; - // Fetch string table from REST. - response = restRequest("CONFIG_GET_STRINGS", NULL); - if (!response) { + // Fetch string table from DB. + if (dbTableGet("strings", table) == SUCCESS) { + while (arrlen(table)) { + record = table[0]; + arrdel(table, 0); + // Strings are encoded in a single buffer as: KEY\0DATA\0 + x = strlen(record->name); + y = strlen(record->data); + length = x + y + 2; + buffer = (char *)malloc(length); + if (!buffer) { + consoleMessageQueue("%ld: Unable to allocate buffer for string packet!\n", client->threadIndex); + break; + } + memcpy(buffer, record->name, x + 1); + memcpy(&buffer[x + 1], record->data, y + 1); + // Build packet. + encoded.control = PACKET_CONTROL_DAT; + encoded.packetType = PACKET_TYPE_STRING; + encoded.channel = data->channel; + encoded.encrypt = 0; + packetEncode(client->packetThreadData, &encoded, buffer, length); + // Send it. + packetSend(client->packetThreadData, &encoded); + DEL(buffer); + DEL(record->name); + DEL(record->data); + DEL(record); + consoleMessageQueue("PACKET_TYPE_STRING [%s] sent.\n", record->name); + } + arrfree(table); + } else { consoleMessageQueue("%ld: Unable to fetch strings!\n", client->threadIndex); return; } - strings = restHelperConfigStringMapGet(response); - if (!strings) { - consoleMessageQueue("%ld: Unable to map strings!\n", client->threadIndex); - return; - } - restRelease(response); - // Send string table to client. - for (i=0; i<(unsigned)shlen(strings); i++) { - // Strings are encoded in a single buffer as: KEY\0DATA\0 - x = strlen(strings[i].key); - y = strlen(strings[i].value); - length = x + y + 2; - buffer = (char *)malloc(length); - if (!buffer) { - consoleMessageQueue("%ld: Unable to allocate buffer for string packet!\n", client->threadIndex); - break; + // Fetch number table from DB. + if (dbTableGet("numbers", table) == SUCCESS) { + while (arrlen(table)) { + record = table[0]; + arrdel(table, 0); + // Integers are encoded in a single buffer as: 1234DATA\0 + // Integers are 64 bit until sent to the client when they are truncated to 32. + x = strlen(record->name); + y = atol(record->data); + length = x + 5; + buffer = (char *)malloc(length); + if (!buffer) { + consoleMessageQueue("%ld: Unable to allocate buffer for number packet!\n", client->threadIndex); + break; + } + memcpy(buffer, &y, 4); + memcpy(&buffer[4], record->name, x + 1); + // Build packet. + encoded.control = PACKET_CONTROL_DAT; + encoded.packetType = PACKET_TYPE_NUMBER; + encoded.channel = data->channel; + encoded.encrypt = 0; + packetEncode(client->packetThreadData, &encoded, buffer, length); + // Send it. + packetSend(client->packetThreadData, &encoded); + DEL(buffer); + DEL(record->name); + DEL(record->data); + DEL(record); + consoleMessageQueue("PACKET_TYPE_NUMBER [%s] sent.\n", record->name); } - memcpy(buffer, strings[i].key, x + 1); - memcpy(&buffer[x + 1], strings[i].value, y + 1); - // Build packet. - encoded.control = PACKET_CONTROL_DAT; - encoded.packetType = PACKET_TYPE_STRING; - encoded.channel = data->channel; - encoded.encrypt = 0; - packetEncode(client->packetThreadData, &encoded, buffer, length); - // Send it. - packetSend(client->packetThreadData, &encoded); - DEL(buffer); - consoleMessageQueue("PACKET_TYPE_STRING [%s] sent.\n", strings[i].key); - } - restHelperConfigStringMapRelease(strings); - - // Fetch number table from REST. - response = restRequest("CONFIG_GET_NUMBERS", NULL); - if (!response) { + arrfree(table); + } else { consoleMessageQueue("%ld: Unable to fetch numbers!\n", client->threadIndex); return; } - integers = restHelperConfigIntegerMapGet(response); - if (!integers) { - consoleMessageQueue("%ld: Unable to map numbers!\n", client->threadIndex); - return; - } - restRelease(response); - - // Send number table to client. - for (i=0; i<(unsigned)shlen(integers); i++) { - // Integers are encoded in a single buffer as: 1234DATA\0 - // Integers are 64 bit until sent to the client when they are truncated to 32. - x = strlen(integers[i].key); - y = integers[i].value; // 64->32 - length = x + 5; - buffer = (char *)malloc(length); - if (!buffer) { - consoleMessageQueue("%ld: Unable to allocate buffer for number packet!\n\r", client->threadIndex); - break; - } - memcpy(buffer, &y, 4); - memcpy(&buffer[4], integers[i].key, x + 1); - // Build packet. - encoded.control = PACKET_CONTROL_DAT; - encoded.packetType = PACKET_TYPE_NUMBER; - encoded.channel = data->channel; - encoded.encrypt = 0; - packetEncode(client->packetThreadData, &encoded, buffer, length); - // Send it. - packetSend(client->packetThreadData, &encoded); - DEL(buffer); - consoleMessageQueue("PACKET_TYPE_NUMBER [%s] sent.\n", integers[i].key); - } - restHelperConfigIntegerMapRelease(integers); // Build PROCEED packet. encoded.control = PACKET_CONTROL_DAT; diff --git a/server/src/console.c b/server/src/console.c index eef9764..6831e8e 100644 --- a/server/src/console.c +++ b/server/src/console.c @@ -69,7 +69,7 @@ void consoleRun(void) { terminalModeConioSet(); - while (_running) { + while (__running) { if (kbhit()) { @@ -120,7 +120,7 @@ void consoleRun(void) { commandOk = 1; } if (!strcasecmp(command, "SHUTDOWN")) { - _running = 0; + __running = 0; commandOk = 1; } // Did we grok it? diff --git a/server/src/database.c b/server/src/database.c new file mode 100644 index 0000000..cd1547c --- /dev/null +++ b/server/src/database.c @@ -0,0 +1,342 @@ +/* + * Kangaroo Punch MultiPlayer Game Server Mark II + * Copyright (C) 2020-2021 Scott Duensing + * + * 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 3 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 . + * + */ + + +#include +#include +#include + +#include "array.h" +#include "database.h" + + +#define STATEMENT_MAX 2048 + + +static MYSQL *_sql = NULL; +static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER; + + +uint8_t dbConnect(char *host, uint16_t port, char *database, char *user, char *password) { + uint8_t reconnect = 1; + + if (_sql == NULL) { + _sql = mysql_init(NULL); + mysql_options(_sql, MYSQL_OPT_RECONNECT, (const void *)&reconnect); + if (mysql_real_connect(_sql, host, user, password, database, port, NULL, 0) == NULL) { + logWrite("dbConnect: %s\n", mysql_error(_sql)); + return FAIL; + } + if (pthread_mutex_init(&_mutex, NULL)) { + logWrite("dbConnect: SQL mutex creation failed.\n"); + return FAIL; + } + return SUCCESS; + } + return FAIL; +} + + +uint8_t dbDisconnect(void) { + if (_sql) { + mysql_close(_sql); + _sql = NULL; + pthread_mutex_destroy(&_mutex); + return SUCCESS; + } + + return FAIL; +} + + +uint8_t dbSettingsStringGet(char *key, char *value, uint32_t max) { + char statement[STATEMENT_MAX]; + char *p = statement; + MYSQL_RES *result = NULL; + MYSQL_ROW row; + int count; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + p += sprintf(p, "SELECT data FROM config WHERE name='"); + p += mysql_real_escape_string(_sql, p, key, strlen(key)); + p += sprintf(p, "'"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbConfigValueGet: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + result = mysql_store_result(_sql); + count = mysql_num_rows(result); + if (count != 1) { + logWrite("dbConfigValueGet: Too many rows returned: %d.\n", count); + mysql_free_result(result); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + if ((row = mysql_fetch_row(result)) == NULL) { + logWrite("dbConfigValueGet: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + strncpy(value, row[0], max); + + mysql_free_result(result); + + pthread_mutex_unlock(&_mutex); + + return SUCCESS; +} + + +uint8_t dbSettingsValueGet(char *key, int32_t *value) { + char temp[DB_CONFIG_ITEM_SIZE] = { 0 }; + + if (dbSettingsStringGet(key, temp, DB_CONFIG_ITEM_SIZE) == FAIL) return FAIL; + + *value = atoi(temp); + + return SUCCESS; +} + + +uint8_t dbTableGet(char *which, DBTableT **table) { + char statement[STATEMENT_MAX]; + char *p = statement; + MYSQL_RES *result = NULL; + MYSQL_ROW row; + int count; + DBTableT *record = NULL; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + p += sprintf(p, "SELECT name, data FROM "); + p += mysql_real_escape_string(_sql, p, which, strlen(which)); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbTableGet: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + result = mysql_store_result(_sql); + for (count = 0; count < (int)mysql_num_rows(result); count++) { + if ((row = mysql_fetch_row(result)) == NULL) { + logWrite("dbTableGet: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + NEW(DBTableT, record); + record->name = strdup(row[0]); + record->data = strdup(row[1]); + arrput(table, record); + } + + mysql_free_result(result); + + pthread_mutex_unlock(&_mutex); + + return SUCCESS; +} + + +uint8_t dbUserCreate(char *first, char *last, char *user, char *pass, char *email) { + char statement[STATEMENT_MAX]; + char *p = statement; + char *hash = NULL; + char *salt = NULL; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + // Not thread safe. Protected by SQL _mutex. + salt = crypt_gensalt(0, 0, 0, 0); + hash = crypt(pass, salt); + + // We don't pass 'enabled' into the database and instead rely on the default value. + p += sprintf(p, "INSERT INTO users (first, last, user, pass, email) VALUES ('"); + p += mysql_real_escape_string(_sql, p, first, strlen(first)); + p += sprintf(p, "', '"); + p += mysql_real_escape_string(_sql, p, last, strlen(last)); + p += sprintf(p, "', '"); + p += mysql_real_escape_string(_sql, p, user, strlen(user)); + p += sprintf(p, "', '"); + p += mysql_real_escape_string(_sql, p, hash, strlen(hash)); + p += sprintf(p, "', '"); + p += mysql_real_escape_string(_sql, p, email, strlen(email)); + p += sprintf(p, "')"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbUserCreate: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + pthread_mutex_unlock(&_mutex); + + return SUCCESS; +} + + +uint8_t dbUserEmailExists(char *email) { + char statement[STATEMENT_MAX]; + char *p = statement; + MYSQL_RES *result = NULL; + int count; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + p += sprintf(p, "SELECT id FROM users WHERE email='"); + p += mysql_real_escape_string(_sql, p, email, strlen(email)); + p += sprintf(p, "'"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbUserEmailExists: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + result = mysql_store_result(_sql); + count = mysql_num_rows(result); + if (count != 1) { + logWrite("dbUserEmailExists: Too many rows returned: %d.\n", count); + mysql_free_result(result); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + mysql_free_result(result); + + pthread_mutex_unlock(&_mutex); + + return SUCCESS; +} + + +uint8_t dbUserLogin(char *user, char *password) { + char statement[STATEMENT_MAX]; + char *p = statement; + MYSQL_RES *result = NULL; + MYSQL_ROW row; + int count; + uint8_t status = FAIL; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + p += sprintf(p, "SELECT pass FROM users WHERE user='"); + p += mysql_real_escape_string(_sql, p, user, strlen(user)); + p += sprintf(p, "' AND enabled = 1"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbUserLogin: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + result = mysql_store_result(_sql); + count = mysql_num_rows(result); + if (count != 1) { + logWrite("dbUserLogin: Too many rows returned: %d.\n", count); + mysql_free_result(result); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + if ((row = mysql_fetch_row(result)) == NULL) { + logWrite("dbUserLogin: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + // Not thread safe. Protected by SQL _mutex. + p = crypt(password, row[0]); + if (strcmp(p, password) == 0) status = SUCCESS; + + mysql_free_result(result); + + pthread_mutex_unlock(&_mutex); + + return status; +} + + +uint8_t dbUserNameExists(char *user) { + char statement[STATEMENT_MAX]; + char *p = statement; + MYSQL_RES *result = NULL; + int count; + + pthread_mutex_lock(&_mutex); + + if (!_sql) { + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + p += sprintf(p, "SELECT id FROM users WHERE user='"); + p += mysql_real_escape_string(_sql, p, user, strlen(user)); + p += sprintf(p, "'"); + if (mysql_real_query(_sql, statement, p - statement) != 0) { + logWrite("dbUserNameExists: %s\n", mysql_error(_sql)); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + result = mysql_store_result(_sql); + count = mysql_num_rows(result); + if (count != 1) { + logWrite("dbUserNameExists: Too many rows returned: %d.\n", count); + mysql_free_result(result); + pthread_mutex_unlock(&_mutex); + return FAIL; + } + + mysql_free_result(result); + + pthread_mutex_unlock(&_mutex); + + return SUCCESS; +} + + + diff --git a/server/src/database.h b/server/src/database.h new file mode 100644 index 0000000..56f254a --- /dev/null +++ b/server/src/database.h @@ -0,0 +1,48 @@ +/* + * Kangaroo Punch MultiPlayer Game Server Mark II + * Copyright (C) 2020-2021 Scott Duensing + * + * 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 3 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 . + * + */ + + +#ifndef DATABASE_H +#define DATABASE_H + + +#include "os.h" + + +#define DB_CONFIG_ITEM_SIZE 1024 + + +typedef struct DBTableS { + char *name; + char *data; +} DBTableT; + + +uint8_t dbConnect(char *host, uint16_t port, char *database, char *user, char *password); +uint8_t dbDisconnect(void); +uint8_t dbSettingsStringGet(char *key, char *value, uint32_t max); +uint8_t dbSettingsValueGet(char *key, int32_t *value); +uint8_t dbTableGet(char *which, DBTableT **table); +uint8_t dbUserCreate(char *first, char *last, char *user, char *pass, char *email); +uint8_t dbUserEmailExists(char *email); +uint8_t dbUserLogin(char *user, char *password); +uint8_t dbUserNameExists(char *user); + + +#endif // DATABASE_H diff --git a/server/src/main.c b/server/src/main.c index aa92fec..8d99bcf 100644 --- a/server/src/main.c +++ b/server/src/main.c @@ -23,39 +23,50 @@ #include "stddclmr.h" #include "server.h" #include "rest.h" +#include "database.h" #include "thirdparty/ini/src/ini.h" -uint8_t _running = 1; // Exported in os.h +// Exported in os.h +uint8_t __running = 1; +uint8_t __restAvailable = 1; // "Config" items come from the INI file. "Settings" are from the database. -static char *_configUser = NULL; -static char *_configPassword = NULL; -static char *_configREST = NULL; +static char *_configServer = NULL; +static uint16_t _configPort = 0; +static char *_configDatabase = NULL; +static char *_configUser = NULL; +static char *_configPassword = NULL; -static void configRead(char *file); -static void configWrite(char *file); +static void configRead(char *file); +static void configWrite(char *file); static void configRead(char *file) { ini_t *ini = NULL; + // Numeric defaults. + _configPort = 16550; + ini = ini_load(file); if (ini) { + ini_sget(ini, "SERVER", "PORT", "%d", &_configPort); + _configServer = strdup(ini_get(ini, "SERVER", "HOST")); + _configDatabase = strdup(ini_get(ini, "SERVER", "DATA")); _configUser = strdup(ini_get(ini, "SERVER", "USER")); _configPassword = strdup(ini_get(ini, "SERVER", "PASS")); - _configREST = strdup(ini_get(ini, "SERVER", "REST")); ini_free(ini); } // String defaults. + if (!_configServer) strdup("kanga.world"); + if (!_configDatabase) strdup("kpmpgsmkii"); if (!_configUser) strdup(""); if (!_configPassword) strdup(""); - if (!_configREST) strdup("http://localhost:8000/api/kp/kangaworld/v1"); } @@ -66,17 +77,22 @@ static void configWrite(char *file) { if (out) { fprintf(out, "[SERVER]\n" - "REST=%s\n" + "HOST=%s\n" + "PORT=%d\n" + "DATA=%s\n" "USER=%s\n" "PASS=%s\n", - _configREST, + _configServer, + _configPort, + _configDatabase, _configUser, _configPassword ); fclose(out); } - DEL(_configREST); + DEL(_configServer); + DEL(_configDatabase); DEL(_configUser); DEL(_configPassword); } @@ -84,11 +100,15 @@ static void configWrite(char *file) { int main(int argc, char *argv[]) { - char *configFile = NULL; - int64_t settingsMaxClients = 0; - int64_t settingsPortNumber = 0; - int64_t settingsClientVersion = 0; - json_object *response = NULL; + char *configFile = NULL; + char hostname[256] = { 0 }; + char settingsFile[DB_CONFIG_ITEM_SIZE] = { 0 }; + char settingsRest[DB_CONFIG_ITEM_SIZE] = { 0 }; + char settingsUser[DB_CONFIG_ITEM_SIZE] = { 0 }; + char settingsPass[DB_CONFIG_ITEM_SIZE] = { 0 }; + int64_t settingsMaxClients = 0; + int64_t settingsPortNumber = 0; + int64_t settingsClientVersion = 0; (void)argc; @@ -96,18 +116,39 @@ int main(int argc, char *argv[]) { logOpenByHandle(memoryLogHandleGet()); logWrite("%s", "Kangaroo Punch MultiPlayer Game Server Mark II\n"); logWrite("%s", "Copyright (C) 2020-2021 Scott Duensing scott@kangaroopunch.com\n\n"); + + // Load our config. configFile = utilAppNameWithNewExtensionGet(argv[0], "ini"); configRead(configFile); - if (!restStartup(_configREST, _configUser, _configPassword)) { - utilDie("Unable to start REST.\n"); + // Find our hostname. + gethostname(hostname, 256); + + // Get settings entries for this host. + snprintf(settingsFile, DB_CONFIG_ITEM_SIZE, "%s_file", hostname); + snprintf(settingsRest, DB_CONFIG_ITEM_SIZE, "%s_rest", hostname); + snprintf(settingsUser, DB_CONFIG_ITEM_SIZE, "%s_user", hostname); + snprintf(settingsPass, DB_CONFIG_ITEM_SIZE, "%s_pass", hostname); + + // Connect to database. + if (!dbConnect(_configServer, _configPort, _configDatabase, _configUser, _configPassword)) { + utilDie("Unable to connect to database.\n"); } - response = restRequest("CONFIG_GET_CONFIG", NULL); - settingsMaxClients = restHelperConfigIntegerGet(response, "maxClients", 2); - settingsPortNumber = restHelperConfigIntegerGet(response, "portNumber", 16550); - settingsClientVersion = restHelperConfigIntegerGet(response, "clientVersion", 1); - restRelease(response); + // Fetch settings needed to start server. + if (!dbSettingsValueGet("maxClients", (int32_t *)&settingsMaxClients)) utilDie("Unable to load maxClients.\n"); + if (!dbSettingsValueGet("portNumber", (int32_t *)&settingsPortNumber)) utilDie("Unable to load portNumber.\n"); + if (!dbSettingsValueGet("clientVersion", (int32_t *)&settingsClientVersion)) utilDie("Unable to load clientVersion.\n"); + if (!dbSettingsStringGet(settingsFile, settingsFile, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load file location.\n"); + if (!dbSettingsStringGet(settingsRest, settingsRest, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load REST URL.\n"); + if (!dbSettingsStringGet(settingsUser, settingsUser, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load REST user.\n"); + if (!dbSettingsStringGet(settingsPass, settingsPass, DB_CONFIG_ITEM_SIZE)) utilDie("Unable to load REST password.\n"); + + // Start up REST. + if (!restStartup(settingsRest, settingsUser, settingsPass)) { + logWrite("Unable to locate REST API. Web site integration disabled.\n"); + __restAvailable = 0; + } clientStartup(); serverStartup(settingsPortNumber, settingsMaxClients); @@ -117,7 +158,7 @@ int main(int argc, char *argv[]) { consoleRun(); // Console closed. Stop server. - _running = 0; + __running = 0; // Wait for all running threads to shut down. logWrite("Shutting down.\n"); diff --git a/server/src/os.h b/server/src/os.h index f478120..7784c3a 100644 --- a/server/src/os.h +++ b/server/src/os.h @@ -48,7 +48,9 @@ #define FAIL 0 -extern uint8_t _running; // Declared in main.c +// Declared in main.c +extern uint8_t __running; +extern uint8_t __restAvailable; #endif // OS_H diff --git a/server/src/rest.c b/server/src/rest.c index d2f5653..1d7e883 100644 --- a/server/src/rest.c +++ b/server/src/rest.c @@ -46,126 +46,6 @@ static unsigned long restThreadtIdGet(void); static json_object *restUrlPost(json_object *request); -int64_t restHelperConfigIntegerGet(json_object *object, char *name, int64_t defaultValue) { - uint64_t i = 0; - json_object *config = NULL; - json_object *data = NULL; - json_object *item = NULL; - int64_t result = defaultValue; - - config = json_object_object_get(object, "payload"); - if (config) { - data = json_object_object_get(config, "data"); - if (data) { - for (i=0; i 0) { - shdel(map, map[0].key); - } - shfree(map); -} - - -/* -char *restHelperConfigStringGet(json_object *object, char *name, char *defaultValue) { - uint64_t i = 0; - json_object *config = NULL; - json_object *data = NULL; - json_object *item = NULL; - char *result = strdup(defaultValue); - - config = json_object_object_get(object, "payload"); - if (config) { - data = json_object_object_get(config, "data"); - if (data) { - for (i=0; i 0) { - DEL(map[0].value); - shdel(map, map[0].key); - } - shfree(map); -} - - #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" // It's used, but the compiler isn't picking it up because it's via a callback. static void restMutexLocker(int mode, int n, const char *file, int line) { @@ -275,8 +155,9 @@ void restShutdown(void) { uint8_t restStartup(char *url, char *user, char *password) { - uint64_t i; - json_object *response; + uint64_t i = 0; + json_object *response = NULL; + uint8_t result = SUCCESS; curl_global_init(CURL_GLOBAL_ALL); @@ -302,10 +183,10 @@ uint8_t restStartup(char *url, char *user, char *password) { // Is the remote server there? response = restRequest("API_AVAILABLE", NULL); - if (!response) return FAIL; + if (!response) result = FAIL; restRelease(response); - return SUCCESS; + return result; } diff --git a/server/src/rest.h b/server/src/rest.h index 5750e4f..65be06f 100644 --- a/server/src/rest.h +++ b/server/src/rest.h @@ -25,23 +25,6 @@ #include -typedef struct RestStringMapS { - char *key; - char *value; -} RestStringMapT; - -typedef struct RestIntegerMapS { - char *key; - int64_t value; -} RestIntegerMapT; - - -int64_t restHelperConfigIntegerGet(json_object *object, char *name, int64_t defaultValue); -RestIntegerMapT *restHelperConfigIntegerMapGet(json_object *object); -void restHelperConfigIntegerMapRelease(RestIntegerMapT *map); -//char *restHelperConfigStringGet(json_object *object, char *name, char *defaultValue); -RestStringMapT *restHelperConfigStringMapGet(json_object *object); -void restHelperConfigStringMapRelease(RestStringMapT *map); void restRelease(json_object *object); json_object *restRequest(char *command, char *format, ...); void restShutdown(void); diff --git a/server/src/server.c b/server/src/server.c index 8d6e582..2ed98a9 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -25,9 +25,15 @@ #include "packet.h" -ENetHost *_server = NULL; -pthread_t _serverThreadHandle = { 0 }; -void *_serverThreadStatus = NULL; +#define CLIENT_OVERFLOW 8 + + +static ENetHost *_server = NULL; +static pthread_t _serverThreadHandle = { 0 }; +static void *_serverThreadStatus = NULL; +static uint32_t _serverMaxClients = 0; +static uint32_t _serverClients = 0; +static pthread_mutex_t _serverMutex = { 0 }; static void serverPacketSender(char *data, uint32_t length, void *userData); @@ -46,6 +52,10 @@ void serverDisconnectClient(ClientThreadT *client) { pthread_join(client->threadHandle, &status); // Hang up. enet_peer_reset(peer); + // Adjust client count. + pthread_mutex_lock(&_serverMutex); + _serverClients--; + pthread_mutex_unlock(&_serverMutex); } @@ -61,6 +71,7 @@ static void serverPacketSender(char *data, uint32_t length, void *userData) { void serverShutdown(void) { pthread_join(_serverThreadHandle, &_serverThreadStatus); + pthread_mutex_destroy(&_serverMutex); enet_host_destroy(_server); } @@ -69,21 +80,25 @@ void serverStartup(uint32_t port, uint32_t maxClients) { ENetAddress address = { 0 }; pthread_attr_t serverThreadAttributes = { 0 }; + _serverMaxClients = maxClients; + // Tell the packet code how to send packets. packetSenderRegister(serverPacketSender); // Set up listening socket. address.host = ENET_HOST_ANY; address.port = port; - _server = enet_host_create(&address, /* the address to bind the server host to */ - maxClients, /* allow up to XX clients and/or outgoing connections */ - 1, /* allow up to 2 channels to be used, 0 and 1 */ - 0, /* assume any amount of incoming bandwidth */ - 0 /* assume any amount of outgoing bandwidth */ + _server = enet_host_create(&address, /* the address to bind the server host to */ + maxClients + CLIENT_OVERFLOW, /* allow up to XX clients and/or outgoing connections (some extra so we can send "server full" messages) */ + 1, /* allow up to 2 channels to be used, 0 and 1 */ + 0, /* assume any amount of incoming bandwidth */ + 0 /* assume any amount of outgoing bandwidth */ ); if (_server == NULL) utilDie("Unable to open server listening port.\n"); // Start server thread. + memset(&_serverMutex, 0, sizeof(pthread_mutex_t)); + pthread_mutex_init(&_serverMutex, NULL); if (pthread_attr_init(&serverThreadAttributes) != 0) utilDie("Unable to create server thread attributes.\n"); pthread_attr_setdetachstate(&serverThreadAttributes, PTHREAD_CREATE_JOINABLE); if (pthread_create(&_serverThreadHandle, &serverThreadAttributes, serverThread, (void *)_server) != 0) utilDie("Unable to start server thread.\n"); @@ -99,21 +114,35 @@ void *serverThread(void *data) { size_t i = 0; char buffer[2048] = { 0 }; - while (_running) { + while (__running) { while (enet_host_service(server, &event, 1) > 0) { switch (event.type) { case ENET_EVENT_TYPE_NONE: break; case ENET_EVENT_TYPE_CONNECT: + // Is the server full? + if (_serverClients >= _serverMaxClients) { + // Send full banner to client. + packet = enet_packet_create("KPMPGSMKII\rFULL\r", 16, ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(event.peer, 0, packet); + break; + } // Create new client. + pthread_mutex_lock(&_serverMutex); + _serverClients++; + pthread_mutex_unlock(&_serverMutex); NEW(ClientThreadT, client); if (!client) utilDie("Unable to allocate new client.\n"); + // System Stuff. client->packetThreadData = packetThreadDataCreate(event.peer); if (!client->packetThreadData) { utilDie("Unable to allocate packetThreadData for new client.\n"); break; // This break silences an invalid clang warning. } + for (i=0; ipingStats[i] = 0; + memset(&client->packetQueueMutex, 0, sizeof(pthread_mutex_t)); + pthread_mutex_init(&client->packetQueueMutex, NULL); client->threadIndex = nextIndex++; client->running = 1; client->packetQueue = NULL; @@ -122,9 +151,8 @@ void *serverThread(void *data) { client->pingAverage = 0; client->pingHigh = 0; client->pingLow = 9999; - for (i=0; ipingStats[i] = 0; - memset(&client->packetQueueMutex, 1, sizeof(pthread_mutex_t)); - pthread_mutex_init(&client->packetQueueMutex, NULL); + // User State Stuff. + client->authenticated = 0; // Keep our client in the peer data for later. event.peer->data = (void *)client; // Make new thread for this client. diff --git a/shared/log.c b/shared/log.c index 2b32e56..8376d84 100644 --- a/shared/log.c +++ b/shared/log.c @@ -18,6 +18,9 @@ */ +// ***TODO*** On DOS, open and close the log on every write so the file gets updated and we still have data on a crash. + + #include "log.h" @@ -27,6 +30,7 @@ static char *_logBuffer = NULL; static uint16_t _logBufferSize = 0; static logCallback _logCallback = NULL; + void logCallbackSet(logCallback callback) { _logCallback = callback; } diff --git a/shared/packets.h b/shared/packets.h index f83d5bd..650156c 100644 --- a/shared/packets.h +++ b/shared/packets.h @@ -41,11 +41,13 @@ typedef enum PacketTypeE { // Packets received by only the server: PACKET_TYPE_CLIENT_SHUTDOWN, + PACKET_TYPE_FILE_REQUEST, PACKET_TYPE_LOGIN, PACKET_TYPE_PONG, PACKET_TYPE_SIGNUP, // Packets received by only the client: + PACKET_TYPE_FILE_RESPONSE, PACKET_TYPE_LOGIN_RESULT, PACKET_TYPE_NUMBER, PACKET_TYPE_PING, @@ -58,5 +60,13 @@ typedef enum PacketTypeE { PACKET_TYPE_COUNT } PacketTypeT; +typedef enum FileRequestsE { + FILE_REQUEST_UNKNOWN = 0, + FILE_REQUEST_CACHE_CHECK, + FILE_REQUEST_OPEN, + FILE_REQUEST_READ, + FILE_REQUEST_CLOSE +} FileRequestsT; + #endif // PACKETS_H