From 7e848f1e72a3717ae524d912e370c207b0e79c57 Mon Sep 17 00:00:00 2001 From: onefang Date: Sat, 4 Jun 2022 09:49:14 +1000 Subject: Add experimental XMPP chat stuff. --- src/sledjchisl/sledjchisl.c | 263 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 236 insertions(+), 27 deletions(-) diff --git a/src/sledjchisl/sledjchisl.c b/src/sledjchisl/sledjchisl.c index 317d079..4f91fae 100644 --- a/src/sledjchisl/sledjchisl.c +++ b/src/sledjchisl/sledjchisl.c @@ -140,6 +140,9 @@ extern char **environ; #include "lib/fcgi_SC.h" #include "lib/handlekeys.h" +#include "lib/json.h" +#include "lib/json-builder.h" + // Both my_config.h and fcgi_config.h define the same PACKAGE* variables, which we don't use anyway, // I deal with that by using a sed invokation when building fcgi2. @@ -518,7 +521,7 @@ static void showSesh(qgrow_t *reply, sesh *shs) } -char toybuf[4096]; +//char toybuf[4096]; lua_State *L; qhashtbl_t *configs; MYSQL *database, *dbconn; @@ -548,6 +551,7 @@ int startPort = 8002; char *backupIARsim = "Sandbox"; char *rSync = ""; int rSyncPort = 0; +char *webHost = "localhost"; char *webRoot = "/var/www/html"; char *webSocket = "sledjchisl.socket"; char *ToS = "Be good."; @@ -808,6 +812,87 @@ char *myHMACkey(char *key, char *in, boolean b64) } +static void print_depth_shift(int depth) +{ + int j; + for (j=0; j < depth; j++) { + printf(" "); + } +} +static void process_value(json_value* value, int depth); +static void process_object(json_value* value, int depth) +{ + int length, x; + if (value == NULL) { + return; + } + length = value->u.object.length; + for (x = 0; x < length; x++) { + print_depth_shift(depth); + printf("object[%d].name = %s\n", x, value->u.object.values[x].name); + process_value(value->u.object.values[x].value, depth+1); + } +} +static void process_array(json_value* value, int depth) +{ + int length, x; + if (value == NULL) { + return; + } + length = value->u.array.length; + printf("array\n"); + for (x = 0; x < length; x++) { + process_value(value->u.array.values[x], depth); + } +} +static void process_value(json_value* value, int depth) +{ + if (value == NULL) { + return; + } + if (value->type != json_object) { + print_depth_shift(depth); + } + switch (value->type) { + case json_none: + printf("none\n"); + break; + case json_null: + printf("null\n"); + break; + case json_object: + process_object(value, depth+1); + break; + case json_array: + process_array(value, depth+1); + break; + case json_integer: + printf("int: %10ld\n", (long)value->u.integer); + break; + case json_double: + printf("double: %f\n", value->u.dbl); + break; + case json_string: + printf("string: %s\n", value->u.string.ptr); + break; + case json_boolean: + printf("bool: %d\n", value->u.boolean); + break; + } +} +void PrintJSON(json_char* json) +{ + json_value* value = json_parse(json, strlen((char *) json)); + + if (value == NULL) + { + E("Unable to parse data! - %s", (char *) json); + return; + } + process_value(value, 0); + json_value_free(value); +} + // In Lua 5.0 reference manual is a table traversal example at page 29. void PrintTable(lua_State *L) { @@ -4626,8 +4711,17 @@ static void HTMLheader(qgrow_t *reply, char *title) " \n" " %s\n" " \n" + " \n" + " \n" +// " \n" +// " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" + , title); if (DEBUG) @@ -4635,7 +4729,7 @@ static void HTMLheader(qgrow_t *reply, char *title) reply->addstrf(reply, " \n" - " \n" + " \n" " \n" ); reply->addstrf(reply, "
\n"); @@ -4979,10 +5073,10 @@ void HTMLfill(reqData *Rd, enum fragmentType type, char *text, int length) } } -static void HTMLfooter(qgrow_t *reply) +static void HTMLfooter(reqData *Rd) { - reply->addstrf(reply, "
\n"); - reply->addstr(reply, + Rd->reply->addstrf(Rd->reply, " \n"); + Rd->reply->addstr(Rd->reply, "
\n" "

Experimental account manager

\n" "

This account manager system is currently experimental, and under heavy development.   " @@ -4994,16 +5088,48 @@ static void HTMLfooter(qgrow_t *reply) "

You will then be logged off.   Now you have to wait for an admin to approve your new account.   " " They should check with the person you listed as vouching for you first.   They will tell you after they approve your account.

" "

Missing bits that are still being written - editing accounts, listing accounts, deleting accounts.

\n" + "

Experimental chat thingy

\n" + "

In the bottom right corner is an experimental chat thingy, based on Jabber / XMPP.

\n" + "

Click on the 'Toggle chat' button to pop it up, then use your grid user name for the 'XMPP Address:' field.   " + " Your user name has to be in the format 'first.last', two words with a dot in the middle.   " + " Then put your grid account password in the 'Password' field and click the 'Log in' button.   " + " Ignore the 'Don't have a chat account? Create an account' bit.

\n" + "

Note that the chat windows can be resized by dragging their top or left edges, which I suggest you do, coz the default size is too small.

\n" + "

You can also use any other Jabber / XMPP client as well.

\n" + "

Remember this is EXPERIMENTAL, I'll be changing things.   Next I'll make it so you don't have to log in to both this chat thingy and the account page separataly.

" + "

P.S.   Yes, I hate the default theme to.   lol

\n" "
\n"); -// reply->addstr(reply, "
\n
\n"); - reply->addstr(reply, -// "
\n" -// "
\n" - "
\n" +// Rd->reply->addstr(Rd->reply, "
\n
\n"); + Rd->reply->addstrf(Rd->reply, + "
\n" " \n" "
\n" + "
\n" + "
\n" "
\n" - "\n\n"); + "\n" + + "\n" + "\n", webHost, webHost, webHost, webHost); } @@ -6995,7 +7121,7 @@ static void accountWebFooter(reqData *Rd, inputForm *oF) if (0 != Rd->errors->size(Rd->errors)) HTMLlist(Rd->reply, "errors -", Rd->errors); HTMLformEnd(Rd->reply); - HTMLfooter(Rd->reply); + HTMLfooter(Rd); } static void accountAddWeb(reqData *Rd, inputForm *oF, inputValue *oV) @@ -7176,6 +7302,8 @@ static int accountRead(reqData *Rd, char *uuid, char *firstName, char *lastName) d("accountRead() single name |%s| |%s|", first, last); if (NULL == t) t = strchr(first, '+'); + if (NULL == t) + t = strchr(first, '.'); if (NULL != t) { *t++ = '\0'; @@ -8344,6 +8472,47 @@ void account_html(char *file, reqData *Rd, HTMLfile *thisFile) C("Ending dynamic page %s %s", Rd->RUri, form); } +// TODO - This is arse about, I get JSON, but send plain text. Need to set content type ehaders in the mod_auth_custom_http, and maybe send json back as well. +void prosody_mod_auth_custom_http_json(char *file, reqData *Rd, HTMLfile *thisFile) +{ + char *user = getStrH(Rd->body, "username"), *password = getStrH(Rd->body, "password"); + int c = accountRead(Rd, NULL, user, NULL); + + C("Starting dynamic page %s [ %s ]", Rd->RUri, user); +// TODO - check it's HTTPS, and check it came from the same server. Same as account,html does, except no external iFrame allowed this time maybe. +// On the other hand, prosody can't handle 301 redirections? + + if (1 != c) + bitch(Rd, "Cannot validate account.", "Account doesn't exist."); + else + { + char *salt = getStrH(Rd->database, "auth.passwordSalt"), *hash = getStrH(Rd->database, "auth.passwordHash"); + + c = 0; + if ((NULL == password) || ('\0' == password[0])) + bitch(Rd, "Cannot validate account.", "No password supplied."); + else if (('\0' != salt[0]) && ('\0' != hash[0])) + { + D("Comparing passwords. %s %s %s", password, salt, hash); + char *h = checkSLOSpassword(Rd, salt, password, hash, "Passwords are not the same."); + + if (NULL == h) + bitch(Rd, "Cannot validate account.", "Passwords are not the same."); + else + { + I("Authenticated XMPP user %s@%s", user, Rd->Host); + c = 1; + free(h); + } + } + else + bitch(Rd, "Cannot validate account.", "No salted hash."); + } + + Rd->reply->addstrf(Rd->reply, (1 == c) ? "true" : "false"); + C("Ending dynamic page %s", Rd->RUri); +} + void forEachMember(char *verb, simFunction func, simFunction not) { @@ -8550,6 +8719,7 @@ int scanForConfigs(char **cPaths) if ((tmp = configs->getstr(configs, "backupIARsim", false)) != NULL) {backupIARsim = tmp; D("Setting backupIARsim = %s", backupIARsim);} if ((tmp = configs->getstr(configs, "rsync", false)) != NULL) {rSync = tmp; D("Setting rsync = %s", rSync);} if ((vd = configs->getstr(configs, "rsyncPort", false)) != NULL) {rSyncPort = (int) *((float *) vd); D("Setting rsyncPort = %d", rSyncPort);} + if ((tmp = configs->getstr(configs, "webHost", false)) != NULL) {webHost = tmp; D("Setting webHost = %s", webHost);} if ((tmp = configs->getstr(configs, "webRoot", false)) != NULL) {webRoot = tmp; D("Setting webRoot = %s", webRoot);} if ((tmp = configs->getstr(configs, "webSocket", false)) != NULL) {webSocket = tmp; D("Setting webSocket = %s", webSocket);} if ((vd = configs->get (configs, "seshRenew", NULL, false)) != NULL) {seshRenew = (int) *((float *) vd); D("Setting seshRenew = %d", seshRenew);} @@ -9362,11 +9532,12 @@ V("Doing %s for %s '%s' %s %s", modeStrings[currentMode], FLAG(m) ? "member" : // Start of web wrangling section. //////////////////////////////////////////////////////////////////////////////////////////////////// char **initialEnv = environ; - char *tmp0, *tmp1; + char *tmp0, *tmp1, *type; int count = 0, entries, bytes; dynPages = qhashtbl(0, 0); newDynPage("account.html", (pageFunction) account_html); + newDynPage("prosody_mod_auth_custom_http.json", (pageFunction) prosody_mod_auth_custom_http_json); // FCGI_LISTENSOCK_FILENO is the socket to the web server. // STDOUT and STDERR go to the web servers error log, or at least it does in Apache 2 mod_fcgid. @@ -9492,6 +9663,14 @@ t("ignoring QUERY"); Rd->RUri = xmprintf("%s%s", Rd->Script, Rd->Path); } t("BODY"); + +// TODO - is there any sort of content type on the incoming request headers I could maybe use here? + memset(toybuf, 0, sizeof(toybuf)); + snprintf(toybuf, sizeof(toybuf), "%s%s%s", scRoot, webRoot, Rd->Path); + tmp0 = qfile_get_ext(toybuf); + type = mimeTypes->getstr(mimeTypes, tmp0, false); + free(tmp0); + char *Body = NULL; if (Length != NULL) { @@ -9499,16 +9678,51 @@ t("BODY"); Body = xmalloc(len + 1); int c = FCGI_fread(Body, 1, len, FCGI_stdin); if (c != len) - { E("Tried to read %d of the body, only got %d!", len, c); - } Body[len] = '\0'; } else - Length = "0"; - Rd->body = toknize(Body, "=&"); + { + Length = "0"; + Body = xmalloc(1); + Body[0] = '\0'; + } + if (strcmp("application/json", type) == 0) + { + json_char *json = (json_char*) Body; + json_value* value = json_parse(json, strtol(Length, NULL, 10)); + + Rd->body = qhashtbl(0, 0); + if (value == NULL) + E("Unable to parse NULL data!"); + else + { +// PrintJSON(json); + if (value->type == json_object) + { + int length = value->u.object.length, x; + + for (x = 0; x < length; x++) + { + char *name = value->u.object.values[x].name; + + if (value->u.object.values[x].value->type == json_string) + { + D("%s = %s", name, value->u.object.values[x].value->u.string.ptr); + Rd->body->putstr(Rd->body, name, value->u.object.values[x].value->u.string.ptr); + } + + } + } + } + json_value_free(value); + } + else + { + Rd->body = toknize(Body, "=&"); + santize(Rd->body); + } free(Body); - santize(Rd->body); D("%s %s://%s%s -> %s%s%s", Rd->Method, Rd->Scheme, Rd->Host, Rd->RUri, scRoot, webRoot, Rd->Path); D("Started FCGI web request ROLE = %s, body is %s bytes, pid %d.", Role, Length, getpid()); @@ -9555,10 +9769,10 @@ t("BODY"); // Content-Security-Policy can get complex, and I first wrote that when it was very simple. lol if ('\0' != webIframers[0]) Rd->Rheaders->putstrf(Rd->Rheaders, "Content-Security-Policy", - "default-src 'self'; script-src 'none'; form-action 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self' %s", webIframers); + "default-src 'self'; script-src 'self' 'unsafe-inline'; form-action 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self' %s; connect-src 'self' %s:5281/http-bind/;", webIframers, webHost); else { - Rd->Rheaders->putstr(Rd->Rheaders, "Content-Security-Policy", "default-src 'self'; script-src 'none'; form-action 'self'; style-src 'self' 'unsafe-inline'"); + Rd->Rheaders->putstrf(Rd->Rheaders, "Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; form-action 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' %s:5281/http-bind/;", webHost); Rd->Rheaders->putstr(Rd->Rheaders, "X-Frame-Options", "SAMEORIGIN"); // This is deprecated, and is an all or nothing thing. } Rd->Rheaders->putstr(Rd->Rheaders, "X-XSS-Protection", "1;mode=block"); @@ -9573,8 +9787,6 @@ t("BODY"); goto sendReply; } - memset(toybuf, 0, sizeof(toybuf)); - snprintf(toybuf, sizeof(toybuf), "%s%s%s", scRoot, webRoot, Rd->Path); HTMLfile *thisFile = checkHTMLcache(toybuf); if (NULL == thisFile) { @@ -9597,12 +9809,9 @@ t("BODY"); goto sendReply; } - tmp0 = qfile_get_ext(toybuf); - tmp1 = mimeTypes->getstr(mimeTypes, tmp0, false); - free(tmp0); - if (NULL != tmp1) + if (NULL != type) { - if (strncmp("text/", tmp1, 5) != 0) + if (strncmp("text/", type, 5) != 0) { E("Only text formats are supported - %s", toybuf); Rd->Rheaders->putstr(Rd->Rheaders, "Status", "415 Unsupported Media Type"); -- cgit v1.1