From d5b036572ec5058817bf6e1e04a660407a1068a5 Mon Sep 17 00:00:00 2001 From: onefang Date: Thu, 14 May 2020 10:48:05 +1000 Subject: Clean up the cleaning up. Sanitize on input. Use raw stuff internaly. Escape on output. --- src/sledjchisl/sledjchisl.c | 409 ++++++++++++++++++++++++++++---------------- 1 file changed, 266 insertions(+), 143 deletions(-) (limited to 'src/sledjchisl/sledjchisl.c') diff --git a/src/sledjchisl/sledjchisl.c b/src/sledjchisl/sledjchisl.c index 3391ad3..07477db 100644 --- a/src/sledjchisl/sledjchisl.c +++ b/src/sledjchisl/sledjchisl.c @@ -488,6 +488,8 @@ qlist_t *dbRequests; // A better idea, when we spawn tmux or spawn-fcgi, capture STDERR, full log everything to that, filtered log to the tmux console (STDOUT). // Then we can use STDOUT / STDIN to run the console stuff. +// TODO - escape anything that will turn the console into garbage. + // https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences char *logTypes[] = { @@ -633,7 +635,9 @@ int sendTmuxCmd(char *dest, char *cmd) void waitTmuxText(char *dest, char *text) { int i; - char *c = xmprintf("sleep 5; %s %s/%s capture-pane -t %s:'%s' -p | grep -E '%s' 2>&1 > /dev/null", Tcmd, scRun, Tsocket, Tconsole, dest, text); + // Using " for the grep pattern, coz ' might be used in a sim name. +// TODO - should escape \ " ` in text. + char *c = xmprintf("sleep 5; %s %s/%s capture-pane -t %s:'%s' -p | grep -F \"%s\" 2>&1 > /dev/null", Tcmd, scRun, Tsocket, Tconsole, dest, text); D("Waiting for '%s'.", text); do @@ -704,6 +708,25 @@ static int filterSims(struct dirtree *node) return 0; } +// We particularly don't want \ " ` +char *cleanSimName(char *name) +{ + size_t l = strlen(name); + char *ret = xmalloc(l + 1); + int i, j = 0; + + for (i = 0; i < l; i++) + { + char r = name[i]; + + if ((' ' == r) || (isalnum(r) != 0)) + ret[j++] = r; + } + ret[j] = '\0'; + + return ret; +} + simList *getSims() { simList *sims = xmalloc(sizeof(simList)); @@ -2244,7 +2267,7 @@ d(" %s = %s", qstrtrim_head(key), val); return ret; } -void santize(qhashtbl_t *tbl, bool decode) +void santize(qhashtbl_t *tbl) { qhashtbl_obj_t obj; @@ -2254,36 +2277,12 @@ void santize(qhashtbl_t *tbl, bool decode) { char *n = obj.name, *o = (char *) obj.data; - if (decode) - qurl_decode(o); - -// if ((strcmp(n, "password") != 0) && (strcmp(n, "psswd") != 0)) - { - // Poor mans Bobby Tables protection. -// TODO - make this reversable, especially so these things can be used in aboutMe, and come out the other end unscathed. -// qurl_encode doesn't handle \, but does the rest. -// So that means don't qurl_decode it, and encode \\. -// But then I have to qurl_decode everwhere. - o = qstrreplace("tr", o, "'", "_"); - o = qstrreplace("tr", o, "\"", "_"); - o = qstrreplace("tr", o, ";", "_"); - o = qstrreplace("tr", o, "(", "_"); - o = qstrreplace("tr", o, ")", "_"); - } - + qurl_decode(o); tbl->putstr(tbl, n, o); } tbl->unlock(tbl); } -/* -char *unsantize(char *str) -{ - char *ret = qurl_decode(xstrdup(str)); - return ret; -} -*/ - void outize(qgrow_t *reply, qhashtbl_t *tbl, char *label) { reply->addstrf(reply, "%s:
\n
\n", label);
@@ -2296,43 +2295,6 @@ void outize(qgrow_t *reply, qhashtbl_t *tbl, char *label)
   reply->addstr(reply, "
\n"); } -char *displayPrep(char *str) -{ - char *ret = xstrdup(str), *t; - - qurl_decode(ret); - t = qstrreplace("tn", ret, "<", "<"); - free(ret); - ret = NULL; - if (NULL != t) - { - ret = qstrreplace("tn", t, ">", ">"); - if (NULL == ret) - ret = t; - else - free(t); - } - - if (NULL == ret) - ret = xstrdup(str); - - return ret; -} - -char *encodeSlash(char *str) -{ - char *ret = xstrdup(str), *t = qstrreplace("tn", str, "\\", "%5c"); - - if (NULL != t) - { - free(ret); - ret = t; - } - - return ret; -} - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie enum cookieSame { @@ -2476,6 +2438,89 @@ static void HTMLheader(qgrow_t *reply, char *title) HTMLdebug(reply); } +// TODO - maybe escape non printables as well? +char *HTMLentities[] = +{ + "", "", "", "", "", "", "", "", "", // NUL SOH STX ETX EOT ENQ ACK BEL BS + " ", " ", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", // VT FF CR SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US + " ", // Space + "!", """, + "#", "$", + "%", "&", + "'", + "(", ")", + "*", + "+", + ",", + "-", + ".", + "/", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + ":", ";", + "<", "=", ">", + "?", + "@", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "[", "\", "]", + "^", + "_", + "`", + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "{", "|", "}", + "~", + "", // DEL + " " // This is actually 160, not 128, but I hack around that. +}; +static void HTMLescapeString(qgrow_t *reply, char *string) +{ + size_t l = strlen(string); + char *t = xmalloc(l * 10 + 1); + int i, j = 0; + boolean space = FALSE; + + for (i = 0; i < l; i++) + { + int s = string[i]; + + // Alternate long line of spaces with space and  . + if (' ' == s) + { + if (space) + { + s = 128; + space = FALSE; + } + else + space = TRUE; + } + else + { + space = FALSE; + if (128 == s) // The real 128 character. + { + t[j++] = ' '; + continue; + } + } + + if (128 >= s) + { + char *r = HTMLentities[s]; + size_t m = strlen(r); + int k; + + for (k = 0; k < m; k++) + t[j++] = r[k]; + } + else + t[j++] = ' '; + } + t[j] = '\0'; + reply->addstr(reply, t); + free(t); +} + static void HTMLtable(qgrow_t *reply, MYSQL *db, MYSQL_RES *result, char *caption, char *URL, char *id) { char *tbl = ""; @@ -2543,7 +2588,11 @@ static void HTMLtable(qgrow_t *reply, MYSQL *db, MYSQL_RES *result, char *captio static void HTMLhidden(qgrow_t *reply, char *name, char *val) { if ((NULL != val) && ("" != val)) - reply->addstrf(reply, " \n", name, val); + { + reply->addstrf(reply, " addstr(reply, "\">\n"); + } } static void HTMLform(qgrow_t *reply, char *action, char *token) @@ -2596,7 +2645,11 @@ static void HTMLtextArea(qgrow_t *reply, char *name, char *title, int rows, int if ("" != wrap) reply->addstrf(reply, " wrap=\"%s\"", wrap); if ((NULL != val) && ("" != val)) - reply->addstrf(reply, ">%s

\n", val); + { + reply->addstr(reply, ">"); + HTMLescapeString(reply, val); + reply->addstr(reply, "

\n"); + } else reply->addstrf(reply, ">

\n"); } @@ -2605,7 +2658,11 @@ static void HTMLtext(qgrow_t *reply, char *type, char *title, char *name, char * { reply->addstrf(reply, "

  • %s
  • \n", (char *) obj.data); + { + reply->addstr(reply, "
  • "); + HTMLescapeString(reply, (char *) obj.data); + reply->addstr(reply, "
  • \n"); + } list->unlock(list); reply->addstr(reply, "\n"); } @@ -3623,61 +3684,95 @@ systemFolders sysFolders[] = {NULL, -1} }; +boolean writeLuaDouble(reqData *Rd, int fd, char *file, char *name, double number) +{ + boolean ret = TRUE; +// TODO - putting these in Lua as numbers causes lua_tolstring to barf when we read them. Though Lua is supposed to convert between numbers and strings. + char *t = xmprintf(" ['%s'] = '%f',\n", name, number); // NOTE - default precision is 6 decimal places. + size_t l = strlen(t); + + if (l != writeall(fd, t, l)) + { + perror_msg("Writing %s", file); + ret = FALSE; + } + free(t); + return ret; +} + +boolean writeLuaInteger(reqData *Rd, int fd, char *file, char *name, long number) +{ + boolean ret = TRUE; +// TODO - putting these in Lua as numbers causes lua_tolstring to barf when we read them. Though Lua is supposed to convert between numbers and strings. + char *t = xmprintf(" ['%s'] = '%ld',\n", name, number); + size_t l = strlen(t); + + if (l != writeall(fd, t, l)) + { + perror_msg("Writing %s", file); + ret = FALSE; + } + free(t); + return ret; +} + +boolean writeLuaString(reqData *Rd, int fd, char *file, char *name, char *string) +{ + boolean ret = TRUE; + + if (NULL == string) + string = getStrH(Rd->stuff, name); + + size_t l = strlen(string); + char *t0 = xmalloc(l * 2 + 1); + int i, j = 0; + +// TODO - maybe escape other non printables as well? + for (i = 0; i < l; i++) + { + // We don't need to escape [] here, coz we are using '' below. Same applies to ", but do it anyway. + switch(string[i]) + { + case '\n': + case '\\': + case '\'': + case '"': + t0[j++] = '\\'; break; + } + if ('\n' == string[i]) + t0[j++] = 'n'; + else if ('\r' == string[i]) + ; + else + t0[j++] = string[i]; + } + t0[j] = '\0'; + + char *t1 = xmprintf(" ['%s'] = '%s',\n", name, t0); + + l = strlen(t1); + if (l != writeall(fd, t1, l)) + { + perror_msg("Writing %s to %s", name, file); + ret = FALSE; + } + free(t1); + free(t0); + return ret; +} + static void accountWrite(reqData *Rd) { char *uuid = getStrH(Rd->database, "UserAccounts.PrincipalID"); char *file = xmprintf("%s/users/%s.lua", scData, uuid); char *level = getStrH(Rd->database, "UserAccounts.UserLevel"); char *link = (NULL == Rd->lnk) ? "" : Rd->lnk->hashish; - char *about = encodeSlash(getStrH(Rd->stuff, "aboutMe")); - char *voucher = encodeSlash(getStrH(Rd->stuff, "voucher")); - char *tnm = xmprintf( "user = \n" - "{\n" - " ['name']='%s',\n" -// TODO - putting these in Lua as numbers causes lua_tolstring to barf when we read them. Though Lua is supposed to convert between numbers and strings. - " ['created']='%ld',\n" - " ['email']='%s',\n" - " ['title']='%s',\n" - " ['level']='%s',\n" - " ['flags']='%d',\n" - " ['active']='%d',\n" - " ['passwordHash']='%s',\n" - " ['passwordSalt']='%s',\n" - " ['UUID']='%s',\n" - " ['DoB']='%s',\n" - " ['agree']='%s',\n" - " ['adult']='%s',\n" - " ['aboutMe']='%s',\n" - " ['vouched']='%s',\n" - " ['voucher']='%s',\n" - " ['linky-hashish']='%s',\n" - "}\n" - "return user\n", - getStrH(Rd->stuff, "name"), - (strcmp("", getStrH(Rd->stuff, "created")) != 0) ? atol(getStrH(Rd->stuff, "created")) : (long) Rd->shs.timeStamp[1].tv_sec, - getStrH(Rd->stuff, "email"), - getLevel(atoi(level)), - level, - 0, - 1, - getStrH(Rd->stuff, "passwordHash"), - getStrH(Rd->stuff, "passwordSalt"), - uuid, - getStrH(Rd->stuff, "DoB"), - getStrH(Rd->stuff, "agree"), - getStrH(Rd->stuff, "adult"), - about, - "off", - voucher, - link - ); + char *tnm = "user = \n{\n"; struct stat st; int s = stat(file, &st); - int fd = notstdio(xcreate_stdio(file, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR)); size_t l = strlen(tnm); - uuid_t binuuid; uuid_clear(binuuid); @@ -3689,19 +3784,48 @@ static void accountWrite(reqData *Rd) I("Creating user %s.", file); else I("Updating user %s.", file); + if (l != writeall(fd, tnm, l)) perror_msg("Writing %s", file); else { char *name = Rd->stuff->getstr(Rd->stuff, "name", true); - char *nm = xmprintf("%s/users/%s.lua", scData, qstrreplace("tr", name, " ", "_")); - - free(file); - file = xmprintf("%s.lua", uuid); - I("Symlinking %s to %s", file, nm); - if (0 != symlink(file, nm)) - perror_msg("Symlinking %s to %s", file, nm); - free(nm); free(name); + char *end = "}\nreturn user\n"; + + if (!writeLuaString (Rd, fd, file, "name", name)) goto notWritten; + if (!writeLuaInteger(Rd, fd, file, "created", + (strcmp("", getStrH(Rd->stuff, "created")) != 0) ? atol(getStrH(Rd->stuff, "created")) : (long) Rd->shs.timeStamp[1].tv_sec)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "email", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "title", getLevel(atoi(level)))) goto notWritten; + if (!writeLuaString (Rd, fd, file, "level", level)) goto notWritten; + if (!writeLuaInteger(Rd, fd, file, "flags", 0)) goto notWritten; + if (!writeLuaInteger(Rd, fd, file, "active", 1)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "passwordHash", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "passwordSalt", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "UUID", uuid)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "DoB", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "agree", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "adult", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "aboutMe", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "vouched", "off")) goto notWritten; + if (!writeLuaString (Rd, fd, file, "voucher", NULL)) goto notWritten; + if (!writeLuaString (Rd, fd, file, "link", link)) goto notWritten; + l = strlen(end); + if (l != writeall(fd, end, l)) + perror_msg("Writing %s", file); + else + { + char *nm = xmprintf("%s/users/%s.lua", scData, qstrreplace("tr", name, " ", "_")); + + free(file); + file = xmprintf("%s.lua", uuid); + I("Symlinking %s to %s", file, nm); + if (0 != symlink(file, nm)) + perror_msg("Symlinking %s to %s", file, nm); + free(nm); + } +notWritten: + free(name); } xclose(fd); @@ -3925,9 +4049,6 @@ T(c); } else W("Not writing NULL UUID user!"); - free(tnm); - free(voucher); - free(about); free(file); } @@ -4554,7 +4675,7 @@ static int emailValidate(reqData *Rd, inputForm *iF, inputValue *iV) } static void emailWeb(reqData *Rd, inputForm *oF, inputValue *oV) { - HTMLtext(Rd->reply, "email", oV->field->title, oV->field->name, displayPrep(getStrH(Rd->stuff, oV->field->name)), oV->field->viewLength, oV->field->maxLength, oV->field->flags & FLD_REQUIRED); + HTMLtext(Rd->reply, "email", oV->field->title, oV->field->name, getStrH(Rd->stuff, oV->field->name), oV->field->viewLength, oV->field->maxLength, oV->field->flags & FLD_REQUIRED); Rd->reply->addstrf(Rd->reply, "

    An email will be sent from %s@%s, and it might be in your spam folder, coz these sorts of emails sometimes end up there.   " "You should add that email address to your contacts, or otherwise let it through your spam filter.

    ", "grid_no_reply", Rd->Host); @@ -4748,9 +4869,10 @@ static int voucherValidate(reqData *Rd, inputForm *oF, inputValue *oV) if ((0 == ret) && (NULL != voucher)) { - char *t = qurl_encode(voucher, strlen(voucher)); - Rd->stuff->putstr(Rd->stuff, "voucher", t); - free(t); +// char *t = qurl_encode(voucher, strlen(voucher)); +// Rd->stuff->putstr(Rd->stuff, "voucher", t); + Rd->stuff->putstr(Rd->stuff, "voucher", voucher); +// free(t); } return ret; @@ -4773,9 +4895,10 @@ static int aboutMeValidate(reqData *Rd, inputForm *oF, inputValue *oV) if ((0 == ret) && (NULL != about)) { - char *t = qurl_encode(about, strlen(about)); - Rd->stuff->putstr(Rd->stuff, "aboutMe", t); - free(t); +// char *t = qurl_encode(about, strlen(about)); +// Rd->stuff->putstr(Rd->stuff, "aboutMe", t); + Rd->stuff->putstr(Rd->stuff, "aboutMe", about); +// free(t); } return ret; @@ -4880,15 +5003,13 @@ static void accountViewWeb(reqData *Rd, inputForm *oF, inputValue *oV) { char *name = getStrH(Rd->database, "Lua.name"), *level = getStrH(Rd->database, "UserAccounts.UserLevel"), - *email = displayPrep(getStrH(Rd->database, "UserAccounts.Email")), - *voucher = displayPrep(getStrH(Rd->database, "Lua.voucher")), - *about = displayPrep(getStrH(Rd->database, "Lua.aboutMe")); + *email = getStrH(Rd->database, "UserAccounts.Email"), + *voucher = getStrH(Rd->database, "Lua.voucher"), + *about = getStrH(Rd->database, "Lua.aboutMe"); time_t crtd = atol(getStrH(Rd->database, "UserAccounts.Created")); accountWebHeaders(Rd, oF); accountWebFields(Rd, oF, oV); -// TODO - still need to encode < > as < u> for email, voucher, and about. -// TODO - dammit, qurl_decode returns the string length, and decodes the string in place. Rd->reply->addstrf(Rd->reply, "

    Name : %s

    ", name); Rd->reply->addstrf(Rd->reply, "

    Title / level : %s / %s

    ", getLevel(atoi(level)), level); Rd->reply->addstrf(Rd->reply, "

    Date of birth : %s

    ", getStrH(Rd->database, "Lua.DoB")); @@ -4909,9 +5030,9 @@ static void accountEditWeb(reqData *Rd, inputForm *oF, inputValue *oV) { char *name = getStrH(Rd->database, "Lua.name"), *level = getStrH(Rd->database, "UserAccounts.UserLevel"), - *email = displayPrep(getStrH(Rd->database, "UserAccounts.Email")), - *voucher = displayPrep(getStrH(Rd->database, "Lua.voucher")), - *about = displayPrep(getStrH(Rd->database, "Lua.aboutMe")), + *email = getStrH(Rd->database, "UserAccounts.Email"), + *voucher = getStrH(Rd->database, "Lua.voucher"), + *about = getStrH(Rd->database, "Lua.aboutMe"), *lvl = getLevel(atoi(level)); short lv = atoi(level); @@ -6748,12 +6869,12 @@ if ((strcmp("HTTP_COOKIE", ky) == 0) || (strcmp("CONTENT_LENGTH", ky) == 0) || ( t("COOKIES"); Rd->cookies = toknize(Rd->headers->getstr(Rd->headers, "HTTP_COOKIE", false), "=;"); - santize(Rd->cookies, TRUE); + santize(Rd->cookies); if (strcmp("GET", Rd->Method) == 0) { // In theory a POST has body fields INSTEAD of query fields. Especially for ignoring the linky-hashish after the validation page. t("QUERY"); Rd->queries = toknize(Rd->headers->getstr(Rd->headers, "QUERY_STRING", false), "=&"); - santize(Rd->queries, TRUE); + santize(Rd->queries); } else { @@ -6779,7 +6900,7 @@ t("BODY"); Length = "0"; Rd->body = toknize(Body, "=&"); free(Body); - santize(Rd->body, TRUE); + santize(Rd->body); I("%s %s://%s%s -> %s%s", Rd->Method, Rd->Scheme, Rd->Host, Rd->RUri, webRoot, Path); D("Started FCGI web request ROLE = %s, body is %s bytes, pid %d.", Role, Length, getpid()); @@ -7072,10 +7193,12 @@ fcgiDone: if (!checkSimIsRunning(sim)) { + char *nm = cleanSimName(name); + I("%s is starting up.", name); memset(toybuf, 0, sizeof(toybuf)); snprintf(toybuf, sizeof(toybuf), "%s %s/%s new-window -dn '%s' -t '%s:%d' 'cd %s/current/bin; mono OpenSim.exe -inidirectory=%s/config/%s'", - Tcmd, scRun, Tsocket, name, Tconsole, i + 1, scRoot, scRoot, sim); + Tcmd, scRun, Tsocket, nm, Tconsole, i + 1, scRoot, scRoot, sim); int r = system(toybuf); if (!WIFEXITED(r)) E("tmux new-window command failed!"); @@ -7083,7 +7206,7 @@ fcgiDone: { memset(toybuf, 0, sizeof(toybuf)); snprintf(toybuf, sizeof(toybuf), "INITIALIZATION COMPLETE FOR %s", name); - waitTmuxText(name, toybuf); + waitTmuxText(nm, toybuf); I("%s is done starting up.", name); la = waitLoadAverage(la, loadAverageInc, simTimeOut); } -- cgit v1.1