aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/sledjchisl/sledjchisl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sledjchisl/sledjchisl.c')
-rw-r--r--src/sledjchisl/sledjchisl.c2839
1 files changed, 1820 insertions, 1019 deletions
diff --git a/src/sledjchisl/sledjchisl.c b/src/sledjchisl/sledjchisl.c
index 1c98575..2bc6719 100644
--- a/src/sledjchisl/sledjchisl.c
+++ b/src/sledjchisl/sledjchisl.c
@@ -308,7 +308,7 @@ qhashtbl_t *HTMLfileCache = NULL;
308 308
309typedef struct _reqData reqData; 309typedef struct _reqData reqData;
310 310
311 311/*
312typedef int (*fieldValidFunc) (reqData *Rd, qhashtbl_t *data, char *name); 312typedef int (*fieldValidFunc) (reqData *Rd, qhashtbl_t *data, char *name);
313typedef struct _validFunc validFunc; 313typedef struct _validFunc validFunc;
314struct _validFunc 314struct _validFunc
@@ -316,6 +316,7 @@ struct _validFunc
316 char *name, *title; 316 char *name, *title;
317 fieldValidFunc func; 317 fieldValidFunc func;
318}; 318};
319
319qlisttbl_t *fieldValidFuncs = NULL; 320qlisttbl_t *fieldValidFuncs = NULL;
320static void newValidFunc(char *name, char *title, fieldValidFunc func) 321static void newValidFunc(char *name, char *title, fieldValidFunc func)
321{ 322{
@@ -324,6 +325,7 @@ static void newValidFunc(char *name, char *title, fieldValidFunc func)
324 fieldValidFuncs->put(fieldValidFuncs, vf->name, vf, sizeof(validFunc)); 325 fieldValidFuncs->put(fieldValidFuncs, vf->name, vf, sizeof(validFunc));
325 free(vf); 326 free(vf);
326} 327}
328*/
327 329
328typedef void *(*pageFunction) (char *file, reqData *Rd, HTMLfile *thisFile); 330typedef void *(*pageFunction) (char *file, reqData *Rd, HTMLfile *thisFile);
329typedef struct _dynPage dynPage; 331typedef struct _dynPage dynPage;
@@ -341,6 +343,7 @@ static void newDynPage(char *name, pageFunction func)
341 free(dp); 343 free(dp);
342} 344}
343 345
346/*
344typedef void *(*pageBuildFunction) (reqData *Rd, char *message); 347typedef void *(*pageBuildFunction) (reqData *Rd, char *message);
345typedef struct _buildPage buildPage; 348typedef struct _buildPage buildPage;
346struct _buildPage 349struct _buildPage
@@ -356,42 +359,6 @@ static void newBuildPage(char *name, pageBuildFunction func, pageBuildFunction e
356 buildPages->put(buildPages, bp->name, bp, sizeof(buildPage)); 359 buildPages->put(buildPages, bp->name, bp, sizeof(buildPage));
357 free(bp); 360 free(bp);
358} 361}
359
360
361/* TODO - there should be some precedence for values overriding values here.
362 Nothing official?
363 https://www.w3.org/standards/webarch/protocols
364 "This intro text is boilerplate for the beta release of w3.org." Fucking useless. Pffft
365 https://www.w3.org/Protocols/
366 Still nothing official, though the ENV / HEADER stuff tends to be about the protocol things, and cookies / body / queries are about the data things.
367
368TODO - I think this is the wrong question, mostly data from different sources is for different reasons.
369
370Also including values from the database.
371
372URL query Values actually provided by the user in the FORM, and other things.
373POST body Values actually provided by the user in the FORM.
374cookies
375 https://stackoverflow.com/questions/4056306/how-to-handle-multiple-cookies-with-the-same-name
376
377headers includes HTTP_COOKIE and QUERY_STRING
378env includes headers and HTTP_COOKIE and QUERY_STRING
379
380database Since all of the above are for updating the database anyway, this goes on the bottom, overridden by all.
381 Though be wary of security stuff.
382
383We don't actually get the headers directly, it's all sent via the env.
384
385http://docs.gantry.org/gantry4/advanced/setby
386 Says that query overrides cookies, but that might be just for their platform.
387
388https://framework.zend.com/manual/1.11/en/zend.controller.request.html
389 Says - "1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV."
390
391
392Sending cookie headers is a special case, multiples can be sent, otherwise headers are singletons, only send one for each name.
393
394local storage? Would be client side Javascript thing not usually sent back to server.
395*/ 362*/
396 363
397#define HMACSIZE EVP_MAX_MD_SIZE * 2 364#define HMACSIZE EVP_MAX_MD_SIZE * 2
@@ -401,8 +368,9 @@ struct _sesh
401{ 368{
402 char salt[256 + 1], seshID[256 + 1], 369 char salt[256 + 1], seshID[256 + 1],
403 sesh[256 + 16 + 10 + 1], munchie[HMACSIZE + 16 + 10 + 1], toke_n_munchie[HMACSIZE + 1], hashish[HMACSIZE + 1], 370 sesh[256 + 16 + 10 + 1], munchie[HMACSIZE + 16 + 10 + 1], toke_n_munchie[HMACSIZE + 1], hashish[HMACSIZE + 1],
404 leaf[HMACSIZE64 + 6 + 1]; 371 leaf[HMACSIZE64 + 6 + 1], *UUID, *name;
405 struct timespec timeStamp[2]; 372 struct timespec timeStamp[2];
373 short level;
406 boolean isLinky; 374 boolean isLinky;
407}; 375};
408 376
@@ -410,13 +378,13 @@ struct _reqData
410{ 378{
411 lua_State *L; 379 lua_State *L;
412 qhashtbl_t *configs, *queries, *body, *cookies, *headers, *valid, *stuff, *database, *Rcookies, *Rheaders; 380 qhashtbl_t *configs, *queries, *body, *cookies, *headers, *valid, *stuff, *database, *Rcookies, *Rheaders;
413 char *Scheme, *Host, *Method, *Script, *RUri, *doit; 381 char *Scheme, *Host, *Method, *Script, *RUri, *doit, *form, *output;
414 sesh shs, *lnk; 382 sesh shs, *lnk;
415 MYSQL *db; 383 MYSQL *db;
416 gridStats *stats; 384 gridStats *stats;
417 qlist_t *errors, *messages; 385 qlist_t *errors, *messages;
418 qgrow_t *reply; 386 qgrow_t *reply;
419 pageBuildFunction func; 387// pageBuildFunction func;
420 struct timespec then; 388 struct timespec then;
421 boolean chillOut, vegOut; 389 boolean chillOut, vegOut;
422}; 390};
@@ -428,14 +396,19 @@ static void showSesh(qgrow_t *reply, sesh *shs)
428 else 396 else
429 reply->addstrf(reply, "Session:<br>\n<pre>\n"); 397 reply->addstrf(reply, "Session:<br>\n<pre>\n");
430 398
431 reply->addstrf(reply, " &nbsp; salt = %s\n", shs->salt); 399 if (NULL != shs->name)
432 reply->addstrf(reply, " &nbsp; seshID = %s\n", shs->seshID); 400 reply->addstrf(reply, " &nbsp; name = %s\n", shs->name);
433 reply->addstrf(reply, " &nbsp; timeStamp = %ld.%ld\n", shs->timeStamp[1].tv_sec, shs->timeStamp[1].tv_nsec); 401 if (NULL != shs->UUID)
434 reply->addstrf(reply, " &nbsp; sesh = %s\n", shs->sesh); 402 reply->addstrf(reply, " &nbsp; UUID = %s\n", shs->UUID);
435 reply->addstrf(reply, " &nbsp; munchie = %s\n", shs->munchie); 403 reply->addstrf(reply, " &nbsp; salt = %s\n", shs->salt);
436 reply->addstrf(reply, " &nbsp; toke_n_munchie = %s\n", shs->toke_n_munchie); 404 reply->addstrf(reply, " &nbsp; seshID = %s\n", shs->seshID);
437 reply->addstrf(reply, " &nbsp; hashish = %s\n", shs->hashish); 405 reply->addstrf(reply, " &nbsp; timeStamp = %ld.%ld\n", shs->timeStamp[1].tv_sec, shs->timeStamp[1].tv_nsec);
438 reply->addstrf(reply, " &nbsp; leaf = %s\n", shs->leaf); 406 reply->addstrf(reply, " &nbsp; sesh = %s\n", shs->sesh);
407 reply->addstrf(reply, " &nbsp; munchie = %s\n", shs->munchie);
408 reply->addstrf(reply, " &nbsp; toke_n_munchie = %s\n", shs->toke_n_munchie);
409 reply->addstrf(reply, " &nbsp; hashish = %s\n", shs->hashish);
410 reply->addstrf(reply, " &nbsp; leaf = %s\n", shs->leaf);
411 reply->addstrf(reply, " &nbsp; level = %d\n", (int) shs->level);
439 reply->addstr(reply, "</pre>\n"); 412 reply->addstr(reply, "</pre>\n");
440} 413}
441 414
@@ -843,7 +816,7 @@ static void PrintEnv(qgrow_t *reply, char *label, char **envp)
843static void printEnv(char **envp) 816static void printEnv(char **envp)
844{ 817{
845 for ( ; *envp != NULL; envp++) 818 for ( ; *envp != NULL; envp++)
846 d("%s", *envp); 819 D("%s", *envp);
847} 820}
848 821
849 822
@@ -905,6 +878,49 @@ I suspect most will be of the form -
905 UPDATE items,month SET items.price=month.price WHERE items.id=month.id; 878 UPDATE items,month SET items.price=month.price WHERE items.id=month.id;
906*/ 879*/
907 880
881static boolean dbConnect()
882{
883 dbconn = mysql_real_connect(database,
884 getStrH(configs, "Data Source"),
885 getStrH(configs, "User ID"),
886 getStrH(configs, "Password"),
887 getStrH(configs, "Database"),
888// 3036, "/var/run/mysqld/mysqld.sock",
889 0, NULL,
890 CLIENT_FOUND_ROWS | CLIENT_LOCAL_FILES | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS);
891 if (NULL == dbconn)
892 {
893 E("mysql_real_connect() failed - %s", mysql_error(database));
894 return FALSE;
895 }
896 return TRUE;
897}
898
899// A general error function that checks for certain errors that mean we should try to connect to the server MariaDB again.
900// https://mariadb.com/kb/en/mariadb-error-codes/
901// 1129? 1152? 1184? 1218? 1927 3032? 4150?
902// "server has gone away" isn't listed there, that's the one I was getting. Pffft
903// It's 2006, https://dev.mysql.com/doc/refman/8.0/en/gone-away.html
904// Though none of the mentioned reasons make sense here.
905// Ah it could be "connection inactive for 8 hours".
906// Which might be why OpenSim opens a new connection for EVERYTHING.
907// TODO - see if I can either find out what the time out is, or just check and re open for each db thing.
908// int mysql_ping(MYSQL * mysql); // https://mariadb.com/kb/en/mysql_ping/
909// "If it has gone down, and global option reconnect is enabled an automatic reconnection is attempted."
910// "Returns zero on success, nonzero if an error occured."
911// "resources bundled to the connection (prepared statements, locks, temporary tables, ...) will be released." sigh
912// Quick'n'dirty until this is properly event driven - have a cron job curl the stats page every hour.
913static boolean dbCheckError(MYSQL *db, char *error, char *sql)
914{
915 int e = mysql_errno(db);
916
917 E("MariaDB error %d - %s: %s\n%s", e, error, mysql_error(db), sql);
918 if (2006 == e)
919 return dbConnect();
920
921 return FALSE;
922}
923
908typedef struct _dbField dbField; 924typedef struct _dbField dbField;
909struct _dbField 925struct _dbField
910{ 926{
@@ -929,19 +945,27 @@ qlisttbl_t *dbGetFields(MYSQL *db, char *table)
929 945
930d("Getting field metadata for %s", table); 946d("Getting field metadata for %s", table);
931 if (mysql_query(db, sql)) 947 if (mysql_query(db, sql))
932 E("Query failed: %s\n%s", mysql_error(db), sql); 948 {
949// E("MariaDB error %d - Query failed 0: %s\n%s", mysql_errno(db), mysql_error(db), sql);
950 if (dbCheckError(db, "Query failed 0", sql))
951 {
952 ret = dbGetFields(db, table);
953 free(sql);
954 return ret;
955 }
956 }
933 else 957 else
934 { 958 {
935 MYSQL_RES *res = mysql_store_result(db); 959 MYSQL_RES *res = mysql_store_result(db);
936 960
937 if (!res) 961 if (!res)
938 E("Couldn't get results set from %s\n %s", mysql_error(db), sql); 962 E("MariaDB error %d - Couldn't get results set from %s\n %s", mysql_errno(db), mysql_error(db), sql);
939 else 963 else
940 { 964 {
941 MYSQL_FIELD *fields = mysql_fetch_fields(res); 965 MYSQL_FIELD *fields = mysql_fetch_fields(res);
942 966
943 if (!fields) 967 if (!fields)
944 E("Failed fetching fields: %s", mysql_error(db)); 968 E("MariaDB error %d - Failed fetching fields: %s", mysql_errno(db), mysql_error(db));
945 else 969 else
946 { 970 {
947 unsigned int i, num_fields = mysql_num_fields(res); 971 unsigned int i, num_fields = mysql_num_fields(res);
@@ -1080,6 +1104,7 @@ d("New SQL statement - %s", req->sql);
1080 goto freeIt; 1104 goto freeIt;
1081 } 1105 }
1082 req->inBind = xzalloc(i * sizeof(MYSQL_BIND)); 1106 req->inBind = xzalloc(i * sizeof(MYSQL_BIND));
1107W("Allocated %d %d inBinds for %s", i, req->inCount, req->sql);
1083 for (i = 0; i < req->inCount; i++) 1108 for (i = 0; i < req->inCount; i++)
1084 { 1109 {
1085 dbField *fld = req->flds->get(req->flds, req->inParams[i], NULL, false); 1110 dbField *fld = req->flds->get(req->flds, req->inParams[i], NULL, false);
@@ -1185,7 +1210,7 @@ d("New SQL statement - %s", req->sql);
1185 prepare_meta_result = mysql_stmt_result_metadata(req->prep); 1210 prepare_meta_result = mysql_stmt_result_metadata(req->prep);
1186 if (!prepare_meta_result) 1211 if (!prepare_meta_result)
1187 { 1212 {
1188 D(" mysql_stmt_result_metadata(), returned no meta information - %s\n", mysql_stmt_error(req->prep)); 1213 D(" mysql_stmt_result_metadata() error %d, returned no meta information - %s\n", mysql_stmt_errno(req->prep), mysql_stmt_error(req->prep));
1189 goto freeIt; 1214 goto freeIt;
1190 } 1215 }
1191 1216
@@ -1221,6 +1246,7 @@ I("count!!!!!!!!!!!!!!!!");
1221 goto freeIt; 1246 goto freeIt;
1222 } 1247 }
1223 req->outBind = xzalloc(i * sizeof(MYSQL_BIND)); 1248 req->outBind = xzalloc(i * sizeof(MYSQL_BIND));
1249W("Allocated %d %d outBinds for %s", i, req->outCount, req->sql);
1224 for (i = 0; i < req->outCount; i++) 1250 for (i = 0; i < req->outCount; i++)
1225 { 1251 {
1226 dbField *fld = req->flds->get(req->flds, req->outParams[i], NULL, false); 1252 dbField *fld = req->flds->get(req->flds, req->outParams[i], NULL, false);
@@ -1335,7 +1361,7 @@ I("count!!!!!!!!!!!!!!!!");
1335 } 1361 }
1336 if (mysql_stmt_bind_result(req->prep, req->outBind)) 1362 if (mysql_stmt_bind_result(req->prep, req->outBind))
1337 { 1363 {
1338 E("Bind failed."); 1364 E("Bind failed error %d.", mysql_stmt_errno(req->prep));
1339 goto freeIt; 1365 goto freeIt;
1340 } 1366 }
1341 } 1367 }
@@ -1467,7 +1493,7 @@ I("count!!!!!!!!!!!!!!!!");
1467 } 1493 }
1468 if (mysql_stmt_bind_param(req->prep, req->inBind)) 1494 if (mysql_stmt_bind_param(req->prep, req->inBind))
1469 { 1495 {
1470 E("Bind failed."); 1496 E("Bind failed error %d.", mysql_stmt_errno(req->prep));
1471 goto freeIt; 1497 goto freeIt;
1472 } 1498 }
1473 1499
@@ -1477,7 +1503,7 @@ d("Execute %s", req->sql);
1477 // do the prepared statement req->prep. 1503 // do the prepared statement req->prep.
1478 if (mysql_stmt_execute(req->prep)) 1504 if (mysql_stmt_execute(req->prep))
1479 { 1505 {
1480 E("Statement execute failed: %s\n", mysql_stmt_error(req->prep)); 1506 E("Statement execute failed %d: %s\n", mysql_stmt_errno(req->prep), mysql_stmt_error(req->prep));
1481 goto freeIt; 1507 goto freeIt;
1482 } 1508 }
1483 1509
@@ -1489,7 +1515,7 @@ d("Execute %s", req->sql);
1489 req->rows->fieldNames = xzalloc(fs * sizeof(char *)); 1515 req->rows->fieldNames = xzalloc(fs * sizeof(char *));
1490 if (mysql_stmt_store_result(req->prep)) 1516 if (mysql_stmt_store_result(req->prep))
1491 { 1517 {
1492 E(" mysql_stmt_store_result() failed %s", mysql_stmt_error(req->prep)); 1518 E(" mysql_stmt_store_result() failed %d: %s", mysql_stmt_errno(req->prep), mysql_stmt_error(req->prep));
1493 goto freeIt; 1519 goto freeIt;
1494 } 1520 }
1495 req->rowCount = mysql_stmt_num_rows(req->prep); 1521 req->rowCount = mysql_stmt_num_rows(req->prep);
@@ -1618,7 +1644,7 @@ freeIt:
1618 if (prepare_meta_result) 1644 if (prepare_meta_result)
1619 mysql_free_result(prepare_meta_result); 1645 mysql_free_result(prepare_meta_result);
1620 if (mysql_stmt_free_result(req->prep)) 1646 if (mysql_stmt_free_result(req->prep))
1621 E("Statement result freeing failed: %s\n", mysql_stmt_error(req->prep)); 1647 E("Statement result freeing failed %d: %s\n", mysql_stmt_errno(req->prep), mysql_stmt_error(req->prep));
1622 1648
1623end: 1649end:
1624 va_end(ap); 1650 va_end(ap);
@@ -1643,6 +1669,7 @@ void dbPull(reqData *Rd, char *table, rowData *rows)
1643 while(me->getnext(me, &obj, false) == true) 1669 while(me->getnext(me, &obj, false) == true)
1644 { 1670 {
1645 where = xmprintf("%s.%s", table, obj.name); 1671 where = xmprintf("%s.%s", table, obj.name);
1672d("dbPull(Rd->database) %s = %s", where, (char *) obj.data);
1646 Rd->database->putstr(Rd->database, where, (char *) obj.data); 1673 Rd->database->putstr(Rd->database, where, (char *) obj.data);
1647// me->remove(me, obj.name); 1674// me->remove(me, obj.name);
1648 free(where); 1675 free(where);
@@ -1655,11 +1682,13 @@ void dbFreeRequest(dbRequest *req)
1655{ 1682{
1656 int i; 1683 int i;
1657 1684
1658 D("Cleaning up prepared database request %s - %s", req->table, req->where); 1685 D("Cleaning up prepared database request %s - %s %d %d", req->table, req->where, req->outCount, req->inCount);
1659 if (NULL != req->outBind) 1686 if (NULL != req->outBind)
1660 { 1687 {
1688d("Free outBind");
1661 for (i = 0; i < req->outCount; i++) 1689 for (i = 0; i < req->outCount; i++)
1662 { 1690 {
1691d("Free outBind %d %s", i, req->sql);
1663 if (NULL != req->outBind[i].buffer) free(req->outBind[i].buffer); 1692 if (NULL != req->outBind[i].buffer) free(req->outBind[i].buffer);
1664 if (NULL != req->outBind[i].length) free(req->outBind[i].length); 1693 if (NULL != req->outBind[i].length) free(req->outBind[i].length);
1665 if (NULL != req->outBind[i].error) free(req->outBind[i].error); 1694 if (NULL != req->outBind[i].error) free(req->outBind[i].error);
@@ -1669,8 +1698,10 @@ void dbFreeRequest(dbRequest *req)
1669 } 1698 }
1670 if (NULL != req->inBind) 1699 if (NULL != req->inBind)
1671 { 1700 {
1701d("Free inBind");
1672 for (i = 0; i < req->inCount; i++) 1702 for (i = 0; i < req->inCount; i++)
1673 { 1703 {
1704d("Free inBind %d %s", i, req->sql);
1674// TODO - this leaks for some bizare reason. 1705// TODO - this leaks for some bizare reason.
1675 if (NULL != req->inBind[i].buffer) free(req->inBind[i].buffer); 1706 if (NULL != req->inBind[i].buffer) free(req->inBind[i].buffer);
1676 if (NULL != req->inBind[i].length) free(req->inBind[i].length); 1707 if (NULL != req->inBind[i].length) free(req->inBind[i].length);
@@ -1705,7 +1736,15 @@ my_ulonglong dbCount(MYSQL *db, char *table, char *where)
1705 sql = xmprintf("SELECT Count(*) FROM %s", table); 1736 sql = xmprintf("SELECT Count(*) FROM %s", table);
1706 1737
1707 if (mysql_query(db, sql)) 1738 if (mysql_query(db, sql))
1708 E("Query failed: %s", mysql_error(db)); 1739 {
1740// E("MariaDB error %d - Query failed 1: %s", mysql_errno(db), mysql_error(db));
1741 if (dbCheckError(db, "Query failed 1", sql))
1742 {
1743 ret = dbCount(db, table, where);
1744 free(sql);
1745 return ret;
1746 }
1747 }
1709 else 1748 else
1710 { 1749 {
1711 MYSQL_RES *result = mysql_store_result(db); 1750 MYSQL_RES *result = mysql_store_result(db);
@@ -1716,7 +1755,7 @@ my_ulonglong dbCount(MYSQL *db, char *table, char *where)
1716 { 1755 {
1717 MYSQL_ROW row = mysql_fetch_row(result); 1756 MYSQL_ROW row = mysql_fetch_row(result);
1718 if (!row) 1757 if (!row)
1719 E("Couldn't get row from %s\n: %s", sql, mysql_error(db)); 1758 E("MariaDB error %d - Couldn't get row from %s\n: %s", mysql_errno(db), sql, mysql_error(db));
1720 else 1759 else
1721 ret = atoll(row[0]); 1760 ret = atoll(row[0]);
1722 mysql_free_result(result); 1761 mysql_free_result(result);
@@ -1752,13 +1791,21 @@ my_ulonglong dbCountJoin(MYSQL *db, char *table, char *select, char *join, char
1752 sql = xmprintf("SELECT %s FROM %s", select, table, join); 1791 sql = xmprintf("SELECT %s FROM %s", select, table, join);
1753 1792
1754 if (mysql_query(db, sql)) 1793 if (mysql_query(db, sql))
1755 E("Query failed: %s", mysql_error(db)); 1794 {
1795// E("MariaDB error %d - Query failed 2: %s", mysql_errno(db), mysql_error(db));
1796 if (dbCheckError(db, "Query failed 2", sql))
1797 {
1798 ret = dbCountJoin(db, table, select, join, where);
1799 free(sql);
1800 return ret;
1801 }
1802 }
1756 else 1803 else
1757 { 1804 {
1758 MYSQL_RES *result = mysql_store_result(db); 1805 MYSQL_RES *result = mysql_store_result(db);
1759 1806
1760 if (!result) 1807 if (!result)
1761 E("Couldn't get results set from %s\n: %s", sql, mysql_error(db)); 1808 E("MariaDB error %d - Couldn't get results set from %s\n: %s", mysql_errno(db), sql, mysql_error(db));
1762 else 1809 else
1763 ret = mysql_num_rows(result); 1810 ret = mysql_num_rows(result);
1764 mysql_free_result(result); 1811 mysql_free_result(result);
@@ -1801,12 +1848,12 @@ MYSQL_RES *dbSelect(MYSQL *db, char *table, char *select, char *join, char *wher
1801 } 1848 }
1802 1849
1803 if (mysql_query(db, sql)) 1850 if (mysql_query(db, sql))
1804 E("Query failed: %s\n%s", mysql_error(db), sql); 1851 E("MariaDB error %d - Query failed 3: %s\n%s", mysql_errno(db), mysql_error(db), sql);
1805 else 1852 else
1806 { 1853 {
1807 ret = mysql_store_result(db); 1854 ret = mysql_store_result(db);
1808 if (!ret) 1855 if (!ret)
1809 E("Couldn't get results set from %s\n %s", mysql_error(db), sql); 1856 E("MariaDB error %d - Couldn't get results set from %s\n %s", mysql_errno(db), mysql_error(db), sql);
1810 } 1857 }
1811 1858
1812 if (-1 == clock_gettime(CLOCK_REALTIME, &now)) 1859 if (-1 == clock_gettime(CLOCK_REALTIME, &now))
@@ -2040,6 +2087,14 @@ void santize(qhashtbl_t *tbl, bool decode)
2040 tbl->unlock(tbl); 2087 tbl->unlock(tbl);
2041} 2088}
2042 2089
2090/*
2091char *unsantize(char *str)
2092{
2093 char *ret = qurl_decode(xstrdup(str));
2094 return ret;
2095}
2096*/
2097
2043void outize(qgrow_t *reply, qhashtbl_t *tbl, char *label) 2098void outize(qgrow_t *reply, qhashtbl_t *tbl, char *label)
2044{ 2099{
2045 reply->addstrf(reply, "%s:<br>\n<pre>\n", label); 2100 reply->addstrf(reply, "%s:<br>\n<pre>\n", label);
@@ -2052,6 +2107,42 @@ void outize(qgrow_t *reply, qhashtbl_t *tbl, char *label)
2052 reply->addstr(reply, "</pre>\n"); 2107 reply->addstr(reply, "</pre>\n");
2053} 2108}
2054 2109
2110char *displayPrep(char *str)
2111{
2112 char *ret = xstrdup(str), *t;
2113
2114 qurl_decode(ret);
2115 t = qstrreplace("tn", ret, "<", "&lt;");
2116 free(ret);
2117 ret = NULL;
2118 if (NULL != t)
2119 {
2120 ret = qstrreplace("tn", t, ">", "&gt;");
2121 if (NULL == ret)
2122 ret = t;
2123 else
2124 free(t);
2125 }
2126
2127 if (NULL == ret)
2128 ret = xstrdup(str);
2129
2130 return ret;
2131}
2132
2133char *encodeSlash(char *str)
2134{
2135 char *ret = xstrdup(str), *t = qstrreplace("tn", str, "\\", "%5c");
2136
2137 if (NULL != t)
2138 {
2139 free(ret);
2140 ret = t;
2141 }
2142
2143 return ret;
2144}
2145
2055 2146
2056// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie 2147// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
2057enum cookieSame 2148enum cookieSame
@@ -2143,6 +2234,21 @@ struct _fragment
2143 char *text; 2234 char *text;
2144}; 2235};
2145 2236
2237static void HTMLdebug(qgrow_t *reply)
2238{
2239 reply->addstrf(reply,
2240 " <p class='hoverItem'>\n"
2241 " <div class='hoverWrapper0'>\n"
2242 " <p>DEBUG</p>\n"
2243 " <div id='hoverShow0'>\n"
2244 " <h1>DEBUG log</h1>\n"
2245 " <!--#echo var=\"DEBUG\" -->\n"
2246 " </div>\n"
2247 " </div>\n"
2248 " </p>\n"
2249 );
2250}
2251
2146static void HTMLheader(qgrow_t *reply, char *title) 2252static void HTMLheader(qgrow_t *reply, char *title)
2147{ 2253{
2148 reply->addstrf(reply, 2254 reply->addstrf(reply,
@@ -2152,6 +2258,7 @@ static void HTMLheader(qgrow_t *reply, char *title)
2152 " <meta charset=\"UTF-8\">\n" 2258 " <meta charset=\"UTF-8\">\n"
2153 " <link rel=\"shortcut icon\" href=\"/SledjHamrIconSmall.png\">\n" 2259 " <link rel=\"shortcut icon\" href=\"/SledjHamrIconSmall.png\">\n"
2154 , title); 2260 , title);
2261 reply->addstrf(reply, " <link type='text/css' rel='stylesheet' href='/SledjChisl.css' media='all' />\n");
2155 2262
2156 if (DEBUG) 2263 if (DEBUG)
2157 reply->addstrf(reply, " <link type='text/css' rel='stylesheet' href='/debugStyle.css' media='all' />\n"); 2264 reply->addstrf(reply, " <link type='text/css' rel='stylesheet' href='/debugStyle.css' media='all' />\n");
@@ -2172,25 +2279,11 @@ static void HTMLheader(qgrow_t *reply, char *title)
2172 " </style>\n" 2279 " </style>\n"
2173 " </head>\n" 2280 " </head>\n"
2174 " <body bgcolor='black' text='white' link='aqua' vlink='fuchsia' alink='red'>\n" 2281 " <body bgcolor='black' text='white' link='aqua' vlink='fuchsia' alink='red'>\n"
2175 " <font face='sans-serif'>" 2282 " <font face='sans-serif'>\n"
2176 ); 2283 );
2177} 2284 reply->addstrf(reply, " <div class='top-left'>\n");
2178 2285 if (DEBUG)
2179static void HTMLdebug(qgrow_t *reply) 2286 HTMLdebug(reply);
2180{
2181 reply->addstrf(reply,
2182 " <div class='top-left'>\n"
2183 " <p class='hoverItem'>\n"
2184 " <div class='hoverWrapper0'>\n"
2185 " <p>DEBUG</p>\n"
2186 " <div id='hoverShow0'>\n"
2187 " <h1>DEBUG log</h1>\n"
2188 " <!--#echo var=\"DEBUG\" -->"
2189 " </div>\n"
2190 " </div>\n"
2191 " </p>\n"
2192 " </div>\n"
2193 );
2194} 2287}
2195 2288
2196static void HTMLtable(qgrow_t *reply, MYSQL *db, MYSQL_RES *result, char *caption, char *URL, char *id) 2289static void HTMLtable(qgrow_t *reply, MYSQL *db, MYSQL_RES *result, char *caption, char *URL, char *id)
@@ -2259,7 +2352,8 @@ static void HTMLtable(qgrow_t *reply, MYSQL *db, MYSQL_RES *result, char *captio
2259 2352
2260static void HTMLhidden(qgrow_t *reply, char *name, char *val) 2353static void HTMLhidden(qgrow_t *reply, char *name, char *val)
2261{ 2354{
2262 reply->addstrf(reply, " <input type=\"hidden\" name=\"%s\" value=\"%s\">\n", name, val); 2355 if ((NULL != val) && ("" != val))
2356 reply->addstrf(reply, " <input type=\"hidden\" name=\"%s\" value=\"%s\">\n", name, val);
2263} 2357}
2264 2358
2265static void HTMLform(qgrow_t *reply, char *action, char *token) 2359static void HTMLform(qgrow_t *reply, char *action, char *token)
@@ -2270,7 +2364,7 @@ static void HTMLform(qgrow_t *reply, char *action, char *token)
2270} 2364}
2271static void HTMLformEnd(qgrow_t *reply) 2365static void HTMLformEnd(qgrow_t *reply)
2272{ 2366{
2273 reply->addstr(reply, " </form>\n"); 2367 reply->addstrf(reply, " </form>\n");
2274} 2368}
2275 2369
2276static void HTMLcheckBox(qgrow_t *reply, char *name, char *title, boolean checked, boolean required) 2370static void HTMLcheckBox(qgrow_t *reply, char *name, char *title, boolean checked, boolean required)
@@ -2288,7 +2382,7 @@ static void HTMLcheckBox(qgrow_t *reply, char *name, char *title, boolean checke
2288 reply->addstrf(reply, "</p>\n"); 2382 reply->addstrf(reply, "</p>\n");
2289} 2383}
2290 2384
2291static void HTMLtextArea(qgrow_t *reply, char *name, char *title, int rows, int cols, int min, int max, char *holder, char *comp, char *spell, char *wrap, char *value, boolean required) 2385static void HTMLtextArea(qgrow_t *reply, char *name, char *title, int rows, int cols, int min, int max, char *holder, char *comp, char *spell, char *wrap, char *val, boolean required, boolean readOnly)
2292{ 2386{
2293 reply->addstrf(reply, " <p><label>%s : <textarea name=\"%s\"", title, name); 2387 reply->addstrf(reply, " <p><label>%s : <textarea name=\"%s\"", title, name);
2294 if (0 < rows) 2388 if (0 < rows)
@@ -2301,6 +2395,8 @@ static void HTMLtextArea(qgrow_t *reply, char *name, char *title, int rows, int
2301 reply->addstrf(reply, " maxlength=\"%d\"", max); 2395 reply->addstrf(reply, " maxlength=\"%d\"", max);
2302 if (required) 2396 if (required)
2303 reply->addstr(reply, " required"); 2397 reply->addstr(reply, " required");
2398 if (readOnly)
2399 reply->addstr(reply, " readonly");
2304 if ("" != holder) 2400 if ("" != holder)
2305 reply->addstrf(reply, " placeholder=\"%s\"", holder); 2401 reply->addstrf(reply, " placeholder=\"%s\"", holder);
2306 if ("" != comp) 2402 if ("" != comp)
@@ -2309,14 +2405,17 @@ static void HTMLtextArea(qgrow_t *reply, char *name, char *title, int rows, int
2309 reply->addstrf(reply, " spellcheck=\"%s\"", spell); 2405 reply->addstrf(reply, " spellcheck=\"%s\"", spell);
2310 if ("" != wrap) 2406 if ("" != wrap)
2311 reply->addstrf(reply, " wrap=\"%s\"", wrap); 2407 reply->addstrf(reply, " wrap=\"%s\"", wrap);
2312 reply->addstrf(reply, ">%s</textarea></label></p>\n", value); 2408 if ((NULL != val) && ("" != val))
2409 reply->addstrf(reply, ">%s</textarea></label></p>\n", val);
2410 else
2411 reply->addstrf(reply, "></textarea></label></p>\n");
2313} 2412}
2314 2413
2315static void HTMLtext(qgrow_t *reply, char *type, char *title, char *name, char *val, int size, int max, boolean required) 2414static void HTMLtext(qgrow_t *reply, char *type, char *title, char *name, char *val, int size, int max, boolean required)
2316{ 2415{
2317 reply->addstrf(reply, " <p><label>%s : <input type=\"%s\" name=\"%s\"", title, type, name); 2416 reply->addstrf(reply, " <p><label>%s : <input type=\"%s\" name=\"%s\"", title, type, name);
2318 if ("" != val) 2417 if ((NULL != val) && ("" != val))
2319 reply->addstrf(reply, "value=\"%s\"", val); 2418 reply->addstrf(reply, " value=\"%s\"", val);
2320 if (0 < size) 2419 if (0 < size)
2321 reply->addstrf(reply, " size=\"%d\"", size); 2420 reply->addstrf(reply, " size=\"%d\"", size);
2322 if (0 < max) 2421 if (0 < max)
@@ -2329,17 +2428,17 @@ static void HTMLtext(qgrow_t *reply, char *type, char *title, char *name, char *
2329static void HTMLselect(qgrow_t *reply, char *title, char *name) 2428static void HTMLselect(qgrow_t *reply, char *title, char *name)
2330{ 2429{
2331 if (NULL == title) 2430 if (NULL == title)
2332 reply->addstrf(reply, " <select name=\"%s\">", name); 2431 reply->addstrf(reply, " <p><select name=\"%s\">", name);
2333 else 2432 else
2334 reply->addstrf(reply, " <p>%s : \n <select name=\"%s\">\n", title, name); 2433 reply->addstrf(reply, " <p><label>%s : \n <select name=\"%s\">\n", title, name);
2335} 2434}
2336static void HTMLselectEnd(qgrow_t *reply) 2435static void HTMLselectEnd(qgrow_t *reply)
2337{ 2436{
2338 reply->addstr(reply, " </select>\n </p>\n"); 2437 reply->addstr(reply, " </select></label></p>\n \n");
2339} 2438}
2340static void HTMLselectEndNo(qgrow_t *reply) 2439static void HTMLselectEndNo(qgrow_t *reply)
2341{ 2440{
2342 reply->addstr(reply, " </select>"); 2441 reply->addstr(reply, " </select></p>");
2343} 2442}
2344 2443
2345static void HTMLoption(qgrow_t *reply, char *title, boolean selected) 2444static void HTMLoption(qgrow_t *reply, char *title, boolean selected)
@@ -2426,9 +2525,28 @@ void HTMLfill(reqData *Rd, enum fragmentType type, char *text, int length)
2426 2525
2427static void HTMLfooter(qgrow_t *reply) 2526static void HTMLfooter(qgrow_t *reply)
2428{ 2527{
2429 reply->addstr(reply, 2528 reply->addstrf(reply, " </div>\n");
2430 " </font>" 2529 reply->addstr(reply,
2431 " </body>\n</html>\n"); 2530 " <div class='top-right'>\n"
2531 " <h1>Test mode</h1>\n"
2532 " <p>This account manager system is currently in test mode, and under heavy development. &nbsp; "
2533 " Which means it not all written yet, and things may break.</p>\n"
2534 " <p>Your mission, should you choose to accept it, is to break it, and report how you broke it, so that onefang can fix it.</p>\n"
2535 " <p>During test mode, no real grid accounts are created, and any accounts created with this will be deleted later. &nbsp; "
2536 " So feel free to create as many test accounts as you need to test things.</p>\n"
2537 " <p>We follow the usual web site registration process, which sends a validation email, with a link to click. &nbsp; "
2538 " However, during this test mode, no emails will be sent, instead a link will be displayed near the top of the page when a user is logged in.</p>\n"
2539 " <p>Missing bits that are still being written - sending the emails, creating real grid accounts, editing accounts, listing accounts, deleting accounts.</p>\n"
2540 " </div>\n");
2541// reply->addstr(reply, " <div class='centre'>\n </div>\n");
2542 reply->addstr(reply,
2543// " <div class='bottom-left'>\n"
2544// " </div>\n"
2545 " <div class='bottom-right'>\n"
2546 " <iframe src='stats.html' style='border:none;height:100%;width:100%;'></iframe>\n"
2547 " </div>\n"
2548 " </font>\n"
2549 "</body>\n</html>\n");
2432} 2550}
2433 2551
2434 2552
@@ -2596,6 +2714,9 @@ HTMLfile *checkHTMLcache(char *file)
2596} 2714}
2597 2715
2598 2716
2717
2718
2719
2599/* TODO - 2720/* TODO -
2600 2721
2601 On new user / password reset. 2722 On new user / password reset.
@@ -2713,7 +2834,7 @@ https://stackoverflow.com/questions/549/the-definitive-guide-to-form-based-websi
2713 2834
2714 2835
2715// Forward declare this here so we can use it in validation functions. 2836// Forward declare this here so we can use it in validation functions.
2716void loginPage(reqData *Rd, char *message); 2837//void loginPage(reqData *Rd, char *message);
2717 2838
2718/* Four choices for the token - (https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) 2839/* Four choices for the token - (https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
2719 https://en.wikipedia.org/wiki/Cross-site_request_forgery 2840 https://en.wikipedia.org/wiki/Cross-site_request_forgery
@@ -2790,10 +2911,13 @@ https://stackoverflow.com/questions/16891729/best-practices-salting-peppering-pa
2790*/ 2911*/
2791 2912
2792 2913
2914qlisttbl_t *accountLevels = NULL;
2915
2916
2793static void bitch(reqData *Rd, char *message, char *log) 2917static void bitch(reqData *Rd, char *message, char *log)
2794{ 2918{
2795 addStrL(Rd->errors, message); 2919 addStrL(Rd->errors, message);
2796 E("%s %s %s %s %s", getStrH(Rd->headers, "REMOTE_ADDR"), getStrH(Rd->stuff, "UUID"), getStrH(Rd->stuff, "name"), message, log); 2920 E("%s %s %s - %s %s", getStrH(Rd->headers, "REMOTE_ADDR"), Rd->shs.UUID, getStrH(Rd->stuff, "name"), message, log);
2797} 2921}
2798 2922
2799/* "A token cookie that references a non-existent session, its value should be replaced immediately to prevent session fixation." 2923/* "A token cookie that references a non-existent session, its value should be replaced immediately to prevent session fixation."
@@ -2806,11 +2930,79 @@ I think this means send a new cookie.
2806static void bitchSession(reqData *Rd, char *message, char *log) 2930static void bitchSession(reqData *Rd, char *message, char *log)
2807{ 2931{
2808 addStrL(Rd->errors, message); 2932 addStrL(Rd->errors, message);
2809 C("%s %s %s %s %s", getStrH(Rd->headers, "REMOTE_ADDR"), getStrH(Rd->stuff, "UUID"), getStrH(Rd->stuff, "name"), message, log); 2933 C("%s %s %s - %s %s", getStrH(Rd->headers, "REMOTE_ADDR"), Rd->shs.UUID, getStrH(Rd->stuff, "name"), message, log);
2810 Rd->vegOut = TRUE; 2934 Rd->vegOut = TRUE;
2811} 2935}
2812 2936
2813 2937
2938// The ancient, insecure since 2011, Second Life / OpenSim password hashing algorithm.
2939char *newSLOSsalt(reqData *Rd)
2940{
2941 char *salt = NULL;
2942 unsigned char *md5hash = xzalloc(17);
2943 char uuid[37];
2944 uuid_t binuuid;
2945
2946 uuid_generate_random(binuuid);
2947 uuid_unparse_lower(binuuid, uuid);
2948 if (!qhashmd5((void *) uuid, strlen(uuid), md5hash))
2949 bitch(Rd, "Internal error.", "newSLOSsalt() - qhashmd5(new uuid) failed.");
2950 else
2951 salt = qhex_encode(md5hash, 16);
2952 free(md5hash);
2953 return salt;
2954}
2955
2956/* TODO - rewrite this -
2957 Don't store things in Rd->stuff. Salt was passed in, and not modified.
2958 Return calculated passHash, not int ret. Returns a NULL if things went wrong.
2959*/
2960char *checkSLOSpassword(reqData *Rd, char *salt, char *password, char *passwordHash, char *fail)
2961{
2962 char *ret = NULL;
2963 int rt = 0;
2964 unsigned char *md5hash = xzalloc(17);
2965 char *hash = NULL, *passHash = NULL;
2966
2967T("checkSLOSpassword(%s, %s, %s, ", password, salt, passwordHash, fail);
2968 // Calculate passHash.
2969 if (!qhashmd5((void *) password, strlen(password), md5hash))
2970 {
2971 bitch(Rd, "Internal error.", "checkSLOSpassword() - qhashmd5(password) failed.");
2972 rt++;
2973 }
2974 else
2975 {
2976 passHash = qhex_encode(md5hash, 16);
2977 hash = xmprintf("%s:%s", passHash, salt);
2978 if (!qhashmd5((void *) hash, strlen(hash), md5hash))
2979 {
2980 bitch(Rd, "Internal error.", "checkSLOSpassword() - qhashmd5(password:salt) failed.");
2981 rt++;
2982 }
2983 else
2984 {
2985 ret = qhex_encode(md5hash, 16);
2986 }
2987 free(hash);
2988 free(passHash);
2989 }
2990
2991 // If one was passed in, compare it.
2992 if ((NULL != ret) && (NULL != passwordHash) && (strcmp(ret, passwordHash) != 0))
2993 {
2994 bitch(Rd, fail, "Password doesn't match passwordHash");
2995 E(" %s %s - %s != %s", password, salt, ret, passwordHash);
2996 rt++;
2997 free(ret);
2998 ret = NULL;
2999 }
3000 free(md5hash);
3001
3002 return ret;
3003}
3004
3005
2814int LuaToHash(reqData *Rd, char *file, char *var, qhashtbl_t *tnm, int ret, struct stat *st, struct timespec *now, char *type) 3006int LuaToHash(reqData *Rd, char *file, char *var, qhashtbl_t *tnm, int ret, struct stat *st, struct timespec *now, char *type)
2815{ 3007{
2816 struct timespec then; 3008 struct timespec then;
@@ -2856,7 +3048,7 @@ int LuaToHash(reqData *Rd, char *file, char *var, qhashtbl_t *tnm, int ret, stru
2856 if (lua_isstring(Rd->L, -1)) 3048 if (lua_isstring(Rd->L, -1))
2857 { 3049 {
2858 tnm->putstr(tnm, n, (char *) lua_tostring(Rd->L, -1)); 3050 tnm->putstr(tnm, n, (char *) lua_tostring(Rd->L, -1));
2859//d("Reading %s = %s", n, getStrH(tnm, n)); 3051d("Lua reading (%s) %s = %s", type, n, getStrH(tnm, n));
2860 } 3052 }
2861 else 3053 else
2862 { 3054 {
@@ -2879,11 +3071,28 @@ int LuaToHash(reqData *Rd, char *file, char *var, qhashtbl_t *tnm, int ret, stru
2879} 3071}
2880 3072
2881 3073
3074char *checkLinky(reqData *Rd)
3075{
3076 char *ret = xstrdup(""), *t0 = getStrH(Rd->stuff, "linky-hashish");
3077
3078 if ('\0' != t0[0])
3079 {
3080 char *t1 = qurl_encode(t0, strlen(t0));
3081 free(ret);
3082 ret = xmprintf("<p><font color='red'><b>You have an email waiting with a linky in it <a href='https://%s%s?hashish=%s'>%s</a>.</b></font></p>\n",
3083 Rd->Host, Rd->RUri, t1, t0);
3084 free(t1);
3085 }
3086 return ret;
3087}
3088
3089
2882static void freeSesh(reqData *Rd, boolean linky, boolean wipe) 3090static void freeSesh(reqData *Rd, boolean linky, boolean wipe)
2883{ 3091{
2884 char *file = NULL; 3092 char *file = NULL;
2885 sesh *shs = &Rd->shs; 3093 sesh *shs = &Rd->shs;
2886 3094
3095T("free sesh %s %s", linky ? "linky" : "session", wipe ? "wipe" : "delete");
2887 if (linky) 3096 if (linky)
2888 { 3097 {
2889 shs = Rd->lnk; 3098 shs = Rd->lnk;
@@ -2914,19 +3123,27 @@ static void freeSesh(reqData *Rd, boolean linky, boolean wipe)
2914 ckh->maxAge = -1; // Should expire immediately. 3123 ckh->maxAge = -1; // Should expire immediately.
2915 3124
2916 qhashtbl_obj_t obj; 3125 qhashtbl_obj_t obj;
2917 memset((void*)&obj, 0, sizeof(obj));
2918 Rd->database->lock(Rd->database);
2919 while(Rd->database->getnext(Rd->database, &obj, false) == true)
2920 Rd->database->remove(Rd->database, obj.name);
2921 Rd->database->unlock(Rd->database);
2922 3126
2923 if (wipe) 3127 if (wipe)
2924 { 3128 {
2925 Rd->stuff->remove(Rd->stuff, "UUID"); 3129 memset((void*)&obj, 0, sizeof(obj));
3130 Rd->database->lock(Rd->database);
3131 while(Rd->database->getnext(Rd->database, &obj, false) == true)
3132 Rd->database->remove(Rd->database, obj.name);
3133 Rd->database->unlock(Rd->database);
3134 shs->name = NULL;
3135 shs->UUID = NULL;
3136 shs->level = -256;
3137// TODO - should I wipe the rest of Rd->shs as well?
2926 Rd->stuff->remove(Rd->stuff, "name"); 3138 Rd->stuff->remove(Rd->stuff, "name");
2927 Rd->stuff->remove(Rd->stuff, "level"); 3139 Rd->stuff->remove(Rd->stuff, "firstName");
3140 Rd->stuff->remove(Rd->stuff, "lastName");
3141 Rd->stuff->remove(Rd->stuff, "email");
2928 Rd->stuff->remove(Rd->stuff, "passwordSalt"); 3142 Rd->stuff->remove(Rd->stuff, "passwordSalt");
2929 Rd->stuff->remove(Rd->stuff, "passwordHash"); 3143 Rd->stuff->remove(Rd->stuff, "passwordHash");
3144 Rd->stuff->remove(Rd->stuff, "passHash");
3145 Rd->stuff->remove(Rd->stuff, "passSalt");
3146 Rd->stuff->remove(Rd->stuff, "linky-hashish");
2930 } 3147 }
2931 3148
2932 if (shs->isLinky) 3149 if (shs->isLinky)
@@ -2935,14 +3152,16 @@ static void freeSesh(reqData *Rd, boolean linky, boolean wipe)
2935 Rd->lnk = NULL; 3152 Rd->lnk = NULL;
2936 } 3153 }
2937 else 3154 else
3155 {
2938 shs->leaf[0] = '\0'; 3156 shs->leaf[0] = '\0';
3157 }
2939 free(file); 3158 free(file);
2940} 3159}
2941 3160
2942static void setToken_n_munchie(reqData *Rd, boolean linky) 3161static void setToken_n_munchie(reqData *Rd, boolean linky)
2943{ 3162{
2944 sesh *shs = &Rd->shs; 3163 sesh *shs = &Rd->shs;
2945 char *file, *link = ""; 3164 char *file;
2946 3165
2947 if (linky) 3166 if (linky)
2948 { 3167 {
@@ -2952,8 +3171,6 @@ static void setToken_n_munchie(reqData *Rd, boolean linky)
2952 else 3171 else
2953 { 3172 {
2954 file = xmprintf("%s/sessions/%s.lua", scCache, shs->leaf); 3173 file = xmprintf("%s/sessions/%s.lua", scCache, shs->leaf);
2955 if (NULL != Rd->lnk)
2956 link = Rd->lnk->hashish;
2957 } 3174 }
2958 3175
2959 struct stat st; 3176 struct stat st;
@@ -2968,45 +3185,77 @@ static void setToken_n_munchie(reqData *Rd, boolean linky)
2968 "{\n" 3185 "{\n"
2969 " ['IP']='%s',\n" 3186 " ['IP']='%s',\n"
2970 " ['salt']='%s',\n" 3187 " ['salt']='%s',\n"
2971 " ['seshID']='%s',\n" 3188 " ['seshID']='%s',\n",
2972 " ['linky-hashishy']='%s',\n",
2973 getStrH(Rd->headers, "REMOTE_ADDR"), 3189 getStrH(Rd->headers, "REMOTE_ADDR"),
2974 shs->salt, 3190 shs->salt,
2975 shs->seshID, 3191 shs->seshID
2976 link
2977 ); 3192 );
2978 char *tnm1 = xmprintf("}\n" 3193 char *tnm1 = xmprintf(" ['name']='%s',\n", shs->name);
3194 char *tnm2 = xmprintf(" ['UUID']='%s',\n", shs->UUID);
3195 char *tnm3 = xmprintf(" ['passHash']='%s',\n", getStrH(Rd->stuff, "passHash"));
3196 char *tnm4 = xmprintf(" ['passSalt']='%s',\n", getStrH(Rd->stuff, "passSalt"));
3197 char *tnm9 = xmprintf("}\n"
2979 "return toke_n_munchie\n"); 3198 "return toke_n_munchie\n");
2980 int fd = notstdio(xcreate_stdio(file, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR)); 3199 int fd = notstdio(xcreate_stdio(file, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR));
2981 size_t l = strlen(tnm0); 3200 size_t l = strlen(tnm0);
2982 3201
3202
2983 if (s) 3203 if (s)
2984 I("Creating session %s.", file); 3204 I("Creating session %s.", file);
2985 else 3205 else
2986 C("Updating session %s.", file); // I don't think updates can occur now. 3206 C("Updating session %s.", file); // I don't think updates can occur now.
3207t("Write shs %s", tnm0);
2987 if (l != writeall(fd, tnm0, l)) 3208 if (l != writeall(fd, tnm0, l))
2988 { 3209 {
2989 perror_msg("Writing %s", file); 3210 perror_msg("Writing %s", file);
2990 freeSesh(Rd, linky, TRUE); 3211 freeSesh(Rd, linky, TRUE);
2991 } 3212 }
2992 3213
2993 qhashtbl_obj_t obj; 3214 if (NULL != shs->name)
3215 {
3216t("Write shs %s", tnm1);
3217 l = strlen(tnm1);
3218 if (l != writeall(fd, tnm1, l))
3219 {
3220 perror_msg("Writing %s", file);
3221 freeSesh(Rd, linky, TRUE);
3222 }
3223 }
3224 if (NULL != shs->UUID)
3225 {
3226t("Write shs %s", tnm2);
3227 l = strlen(tnm2);
3228 if (l != writeall(fd, tnm2, l))
3229 {
3230 perror_msg("Writing %s", file);
3231 freeSesh(Rd, linky, TRUE);
3232 }
3233 }
2994 3234
2995 memset((void*)&obj, 0, sizeof(obj)); 3235 if ('\0' != getStrH(Rd->stuff, "passHash")[0])
2996 Rd->stuff->lock(Rd->stuff); 3236 {
2997 while(Rd->stuff->getnext(Rd->stuff, &obj, false) == true) 3237t("Write shs %s", tnm3);
3238 l = strlen(tnm3);
3239 if (l != writeall(fd, tnm3, l))
3240 {
3241 perror_msg("Writing %s", file);
3242 freeSesh(Rd, linky, TRUE);
3243 }
3244 }
3245
3246 if ('\0' != getStrH(Rd->stuff, "passSalt")[0])
2998 { 3247 {
2999t("stuff %s = %s", obj.name, (char *) obj.data); 3248t("Write shs %s", tnm4);
3000 if (dprintf(fd, " ['%s'] = '%s',\n", obj.name, (char *) obj.data) < 0) 3249 l = strlen(tnm4);
3250 if (l != writeall(fd, tnm4, l))
3001 { 3251 {
3002 perror_msg("Writing %s", file); 3252 perror_msg("Writing %s", file);
3003 freeSesh(Rd, linky, TRUE); 3253 freeSesh(Rd, linky, TRUE);
3004 } 3254 }
3005 } 3255 }
3006 Rd->stuff->unlock(Rd->stuff);
3007 3256
3008 l = strlen(tnm1); 3257 l = strlen(tnm9);
3009 if (l != writeall(fd, tnm1, l)) 3258 if (l != writeall(fd, tnm9, l))
3010 { 3259 {
3011 perror_msg("Writing %s", file); 3260 perror_msg("Writing %s", file);
3012 freeSesh(Rd, linky, TRUE); 3261 freeSesh(Rd, linky, TRUE);
@@ -3014,14 +3263,71 @@ t("stuff %s = %s", obj.name, (char *) obj.data);
3014 // Set the mtime on the file. 3263 // Set the mtime on the file.
3015 futimens(fd, shs->timeStamp); 3264 futimens(fd, shs->timeStamp);
3016 xclose(fd); 3265 xclose(fd);
3266 free(tnm9);
3267 free(tnm2);
3017 free(tnm1); 3268 free(tnm1);
3018 free(tnm0); 3269 free(tnm0);
3019 free(file); 3270 free(file);
3020} 3271}
3021 3272
3022static void createUser(reqData *Rd) 3273
3274static void generateAccountUUID(reqData *Rd)
3275{
3276 // Generate a UUID, check it isn't already being used.
3277 char uuid[37], *where;
3278 uuid_t binuuid;
3279 my_ulonglong users = 0;
3280 int c;
3281
3282 do // UserAccounts.PrincipalID is a unique primary index anyway, but we want the user creation process to be a little on the slow side.
3283 {
3284 struct stat st;
3285
3286 uuid_generate_random(binuuid);
3287 uuid_unparse_lower(binuuid, uuid);
3288 // Try Lua user file.
3289 where = xmprintf("%s/users/%s.lua", scData, uuid);
3290 c = stat(where, &st);
3291 if (c)
3292 users = 1;
3293 free(where);
3294 // Try database.
3295 where = xmprintf("UserAccounts.PrincipalID = '%s'", uuid);
3296 D("Trying new UUID %s.", where);
3297 users = dbCount(Rd->db, "UserAccounts", where);
3298 free(where);
3299 } while (users != 0);
3300 Rd->shs.UUID = xstrdup(uuid);
3301 Rd->shs.level = -200;
3302 Rd->database->putstr(Rd->database, "UserAccounts.PrincipalID", uuid);
3303 Rd->database->putstr(Rd->database, "UserAccounts.Userlevel", "-200");
3304}
3305
3306char *getLevel(reqData *Rd)
3307{
3308 char *ret = "", *lvl = xmprintf("%d", Rd->shs.level);
3309 ret = accountLevels->getstr(accountLevels, lvl, false);
3310 if (NULL == ret)
3311 {
3312 qlisttbl_obj_t obj;
3313
3314 memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
3315 accountLevels->lock(accountLevels);
3316 while(accountLevels->getnext(accountLevels, &obj, NULL, false) == true)
3317 {
3318 if (atoi(obj.name) <= Rd->shs.level)
3319 ret = (char *) obj.data;
3320 }
3321 }
3322 free(lvl);
3323 return ret;
3324}
3325
3326static void accountWrite(reqData *Rd)
3023{ 3327{
3024 char *file = xmprintf("%s/users/%s.lua", scData, getStrH(Rd->stuff, "UUID")); 3328 char *file = xmprintf("%s/users/%s.lua", scData, Rd->shs.UUID);
3329 char *link = (NULL == Rd->lnk) ? "" : Rd->lnk->hashish;
3330 char *about = encodeSlash(getStrH(Rd->stuff, "aboutMe"));
3025 char *tnm = xmprintf( "user = \n" 3331 char *tnm = xmprintf( "user = \n"
3026 "{\n" 3332 "{\n"
3027 " ['name']='%s',\n" 3333 " ['name']='%s',\n"
@@ -3032,32 +3338,33 @@ static void createUser(reqData *Rd)
3032 " ['level']='%d',\n" 3338 " ['level']='%d',\n"
3033 " ['flags']='%d',\n" 3339 " ['flags']='%d',\n"
3034 " ['active']='%d',\n" 3340 " ['active']='%d',\n"
3035 " ['passwordSalt']='%s',\n"
3036 " ['passwordHash']='%s',\n" 3341 " ['passwordHash']='%s',\n"
3342 " ['passwordSalt']='%s',\n"
3037 " ['UUID']='%s',\n" 3343 " ['UUID']='%s',\n"
3038 " ['DoB']='%s-%s',\n" 3344 " ['DoB']='%s',\n"
3039 " ['agree']='%s',\n" 3345 " ['agree']='%s',\n"
3040 " ['adult']='%s',\n" 3346 " ['adult']='%s',\n"
3041 " ['aboutMe']='%s',\n" 3347 " ['aboutMe']='%s',\n"
3042 " ['vouched']='%s',\n" 3348 " ['vouched']='%s',\n"
3349 " ['linky-hashish']='%s',\n"
3043 "}\n" 3350 "}\n"
3044 "return user\n", 3351 "return user\n",
3045 getStrH(Rd->stuff, "name"), 3352 getStrH(Rd->stuff, "name"),
3046 (long) Rd->shs.timeStamp[1].tv_sec, 3353 (strcmp("", getStrH(Rd->stuff, "created")) != 0) ? atol(getStrH(Rd->stuff, "created")) : (long) Rd->shs.timeStamp[1].tv_sec,
3047 getStrH(Rd->body, "email"), 3354 getStrH(Rd->stuff, "email"),
3048 "newbie", 3355 getLevel(Rd),
3049 -200, 3356 Rd->shs.level,
3050 64, 3357 64,
3051 0, 3358 0,
3052 getStrH(Rd->stuff, "passwordSalt"),
3053 getStrH(Rd->stuff, "passwordHash"), 3359 getStrH(Rd->stuff, "passwordHash"),
3054 getStrH(Rd->stuff, "UUID"), 3360 getStrH(Rd->stuff, "passwordSalt"),
3055 getStrH(Rd->body, "year"), 3361 Rd->shs.UUID,
3056 getStrH(Rd->body, "month"), 3362 getStrH(Rd->stuff, "DoB"),
3057 getStrH(Rd->body, "agree"), 3363 getStrH(Rd->stuff, "agree"),
3058 getStrH(Rd->body, "adult"), 3364 getStrH(Rd->stuff, "adult"),
3059 getStrH(Rd->body, "aboutMe"), 3365 about,
3060 "off" 3366 "off",
3367 link
3061 ); 3368 );
3062 3369
3063 struct stat st; 3370 struct stat st;
@@ -3078,13 +3385,15 @@ static void createUser(reqData *Rd)
3078 char *nm = xmprintf("%s/users/%s.lua", scData, qstrreplace("tr", name, " ", "_")); 3385 char *nm = xmprintf("%s/users/%s.lua", scData, qstrreplace("tr", name, " ", "_"));
3079 3386
3080 free(file); 3387 free(file);
3081 file = xmprintf("%s.lua", getStrH(Rd->stuff, "UUID")); 3388 file = xmprintf("%s.lua", Rd->shs.UUID);
3082 I("Symlinking %s to %s", file, nm); 3389 I("Symlinking %s to %s", file, nm);
3083 if (0 != symlink(file, nm)) 3390 if (0 != symlink(file, nm))
3084 perror_msg("Symlinking %s to %s", file, nm); 3391 perror_msg("Symlinking %s to %s", file, nm);
3085 free(nm); free(name); 3392 free(nm); free(name);
3086 } 3393 }
3087 xclose(fd); 3394 xclose(fd);
3395 free(tnm);
3396 free(about);
3088 free(file); 3397 free(file);
3089} 3398}
3090 3399
@@ -3096,11 +3405,12 @@ static sesh *newSesh(reqData *Rd, boolean linky)
3096 uuid_t binuuid; 3405 uuid_t binuuid;
3097 sesh *ret = &Rd->shs; 3406 sesh *ret = &Rd->shs;
3098 3407
3099d("New sesh"); 3408T("new sesh %s %s %s", linky ? "linky" : "session", ret->UUID, ret->name);
3100 if (linky) 3409 if (linky)
3101 { 3410 {
3102 Rd->lnk = xzalloc(sizeof(sesh)); 3411 Rd->lnk = xzalloc(sizeof(sesh));
3103 ret = Rd->lnk; 3412 ret = Rd->lnk;
3413 ret->UUID = Rd->shs.UUID;
3104 } 3414 }
3105 3415
3106 char buf[128]; // 512 bits. 3416 char buf[128]; // 512 bits.
@@ -3145,7 +3455,15 @@ d("New sesh");
3145 free(t1); 3455 free(t1);
3146 qstrcpy(ret->munchie, sizeof(ret->munchie), munchie); 3456 qstrcpy(ret->munchie, sizeof(ret->munchie), munchie);
3147//d("munchie %s", ret->munchie); 3457//d("munchie %s", ret->munchie);
3148 t0 = xmprintf("%s%s", getStrH(Rd->stuff, "UUID"), munchie); 3458// TODO - chicken and egg? Used to be from stuff->UUID.
3459 t1 = ret->UUID;
3460 if (NULL == t1)
3461 {
3462 uuid_clear(binuuid);
3463 uuid_unparse_lower(binuuid, uuid);
3464 ret->UUID = uuid;
3465 }
3466 t0 = xmprintf("%s%s", ret->UUID, munchie);
3149 free(munchie); 3467 free(munchie);
3150 toke_n_munchie = myHMAC(t0, FALSE); 3468 toke_n_munchie = myHMAC(t0, FALSE);
3151 free(t0); 3469 free(t0);
@@ -3154,11 +3472,11 @@ d("New sesh");
3154 hashish = myHMACkey(ret->salt, toke_n_munchie, FALSE); 3472 hashish = myHMACkey(ret->salt, toke_n_munchie, FALSE);
3155 free(toke_n_munchie); 3473 free(toke_n_munchie);
3156 qstrcpy(ret->hashish, sizeof(ret->hashish), hashish); 3474 qstrcpy(ret->hashish, sizeof(ret->hashish), hashish);
3157//d("hashish %s", ret->hashish); 3475d("hashish %s", ret->hashish);
3158 t0 = myHMACkey(getStrH(Rd->configs, "pepper"), hashish, TRUE); 3476 t0 = myHMACkey(getStrH(Rd->configs, "pepper"), hashish, TRUE);
3159 free(hashish); 3477 free(hashish);
3160 qstrcpy(ret->leaf, sizeof(ret->leaf), t0); 3478 qstrcpy(ret->leaf, sizeof(ret->leaf), t0);
3161//d("leaf %s", ret->leaf); 3479d("leaf %s", ret->leaf);
3162 free(t0); 3480 free(t0);
3163 ret->isLinky = linky; 3481 ret->isLinky = linky;
3164 setToken_n_munchie(Rd, linky); 3482 setToken_n_munchie(Rd, linky);
@@ -3170,91 +3488,83 @@ d("New sesh");
3170 return ret; 3488 return ret;
3171} 3489}
3172 3490
3173char *checkLinky(reqData *Rd)
3174{
3175 char *ret = xstrdup(""), *t0 = getStrH(Rd->stuff, "linky-hashish");
3176 3491
3177 if ('\0' != t0[0])
3178 {
3179 char *t1 = qurl_encode(t0, strlen(t0));
3180 free(ret);
3181 ret = xmprintf("<p><font color='red'><b>You have an email waiting with a linky in it <a href='https://%s%s?hashish=%s'>%s</a>.</b></font></p>\n",
3182 Rd->Host, Rd->RUri, t1, t0);
3183 free(t1);
3184 }
3185 return ret;
3186}
3187 3492
3188 3493
3189boolean badBoy(int ret, reqData *Rd, qhashtbl_t *data, char *name, char *value) 3494/* CRUD (Create, Read, Update, Delete)
3190{ 3495CRAP (Create, Replicate, Append, Process)
3191 if (NULL == value) 3496Though I prefer -
3192 value = getStrH(data, name); 3497DAVE (Delete, Add, View, Edit), coz the names are shorter. B-)
3193 if (0 != ret) 3498On the other hand, list or browse needs to be added, which is why they have
3194 { 3499BREAD (Browse, Read, Edit, Add, Delete)
3195 Rd->valid->putstr(Rd->valid, name, "-1"); 3500CRUDL (Create, Read, Update, Delete, List)
3196W("Bad boy %s = %s", name, value); 3501CRUDE (Create, Read, Update, Delete, Experience)
3197 return TRUE; 3502Maybe -
3198 } 3503DAVEE (Delete, Add, View, Edit, Explore)
3199 Rd->stuff->putstr(Rd->stuff, name, value); 3504*/
3200 Rd->valid->putstr(Rd->valid, name, "1");
3201 return FALSE;
3202}
3203 3505
3204int validateThings(reqData *Rd, char *doit, char *name, qhashtbl_t *things) 3506// lua.h has LUA_T* NONE, NIL, BOOLEAN, LIGHTUSERDATA, NUMBER, STRING, TABLE, FUNCTION, USERDATA, THREAD as defines, -1 - 8.
3507// These are the missing ones. Then later we will have prim, mesh, script, sound, terrain, ...
3508#define LUA_TGROUP 42
3509#define LUA_TINTEGER 43
3510#define LUA_TEMAIL 44
3511#define LUA_TPASSWORD 45
3512#define LUA_TFILE 46
3513#define LUA_TIMAGE 47
3514
3515#define FLD_NONE 0
3516#define FLD_EDITABLE 1
3517#define FLD_HIDDEN 2
3518#define FLD_REQUIRED 4
3519
3520typedef struct _inputField inputField;
3521typedef struct _inputSub inputSub;
3522typedef struct _inputForm inputForm;
3523typedef struct _inputValue inputValue;
3524
3525typedef int (*inputFieldValidFunc) (reqData *Rd, inputForm *iF, inputValue *iV);
3526typedef void (*inputFieldShowFunc) (reqData *Rd, inputForm *iF, inputValue *iV);
3527typedef int (*inputSubmitFunc) (reqData *Rd, inputForm *iF, inputValue *iV);
3528typedef void (*inputFormShowFunc) (reqData *Rd, inputForm *iF, inputValue *iV);
3529
3530struct _inputField
3205{ 3531{
3206 int e = 0; 3532 char *name, *title, *help;
3207 qlisttbl_obj_t obj; 3533 inputFieldValidFunc validate; // Alas C doesn't have any anonymous function standard.
3208 3534 inputFieldShowFunc web, console, gui;
3209 D("For function %s, start of %s validation.", doit, name); 3535 inputField **group; // If this is a LUA_TGROUP, then this will be a null terminated array of the fields in the group.
3210 memset((void *) &obj, 0, sizeof(obj)); 3536// database details
3211 fieldValidFuncs->lock(fieldValidFuncs); 3537// lua file details
3212 while(fieldValidFuncs->getnext(fieldValidFuncs, &obj, NULL, false) == true) 3538 signed char type, flags;
3213 { 3539 short editLevel, viewLevel, viewLength, maxLength;
3214 char *t = things->getstr(things, obj.name, false); 3540};
3215 3541struct _inputSub
3216 if (NULL != t) 3542{
3217 { 3543 char *name, *title, *help, *outputForm;
3218 char *nm = obj.name, *v = Rd->valid->getstr(Rd->valid, nm, false); 3544 inputSubmitFunc submit;
3219 int valid = 0; 3545};
3220 validFunc *vf = (validFunc *) obj.data; 3546struct _inputForm
3221 3547{
3222 if (NULL != v) 3548 char *name, *title, *help;
3223 valid = atoi(v); 3549 qlisttbl_t *fields; // qlisttbl coz iteration in order and lookup are important.
3224 3550 qhashtbl_t *subs;
3225 if (0 != valid) // Is it in the valid qhashtbl? 3551 inputFormShowFunc web, eWeb; // display web, console, gui;
3226 { 3552// read function
3227 if (0 < valid) // Positive valid means it's valid, negative means it's invald. 3553// write function
3228 D("Already validated %s - %s.", vf->title, nm); 3554};
3229 else 3555struct _inputValue
3230 D("Already invalidated %s - %s.", vf->title, nm); 3556{
3231 } 3557 inputField *field;
3232 else 3558 void *value; // If this is a LUA_TGROUP, then this will be a null.
3233 { 3559 short valid; // 0 for not yet validated, negative for invalid, positive for valid.
3234 D("Validating %s - %s.", vf->title, nm); 3560 short source, index;
3235 if (vf->func) 3561};
3236 e += vf->func(Rd, things, nm);
3237 else
3238 E("No validation function for %s - %s", vf->title, nm);
3239 }
3240 }
3241 }
3242 fieldValidFuncs->unlock(fieldValidFuncs);
3243 return e;
3244}
3245 3562
3246 3563
3247static int validateSesh(reqData *Rd, qhashtbl_t *data, char *name) 3564static int sessionValidate(reqData *Rd, inputForm *iF, inputValue *iV)
3248{ 3565{
3249 int ret = 0; 3566 int ret = 0;
3250 boolean linky = FALSE; 3567 boolean linky = FALSE;
3251
3252 if ('\0' != Rd->shs.leaf[0])
3253 {
3254 d("Already validated session.");
3255 return ret;
3256 }
3257
3258 char *toke_n_munchie = "", *munchie = "", *hashish = "", *leaf = "", *timeStamp = "", *seshion = "", *seshID = "", *t0, *t1; 3568 char *toke_n_munchie = "", *munchie = "", *hashish = "", *leaf = "", *timeStamp = "", *seshion = "", *seshID = "", *t0, *t1;
3259 3569
3260 // In this case the session stuff has to come from specific places. 3570 // In this case the session stuff has to come from specific places.
@@ -3265,10 +3575,15 @@ static int validateSesh(reqData *Rd, qhashtbl_t *data, char *name)
3265 else 3575 else
3266 { 3576 {
3267 toke_n_munchie = getStrH(Rd->cookies, "toke_n_munchie"); 3577 toke_n_munchie = getStrH(Rd->cookies, "toke_n_munchie");
3268 munchie = getStrH(Rd->body, "munchie"); 3578// munchie = getStrH(Rd->body, "munchie");
3269 hashish = getStrH(Rd->cookies, "hashish"); 3579 hashish = getStrH(Rd->cookies, "hashish");
3270 if (('\0' == toke_n_munchie[0]) || (('\0' == hashish[0]))) 3580 if (('\0' == toke_n_munchie[0]) || (('\0' == hashish[0])))
3271 { 3581 {
3582 if (strcmp("logout", Rd->doit) == 0)
3583 {
3584 d("Not checking session, coz we are logging out.");
3585 return ret;
3586 }
3272 bitchSession(Rd, "Invalid session.", "No or blank hashish or toke_n_munchie."); 3587 bitchSession(Rd, "Invalid session.", "No or blank hashish or toke_n_munchie.");
3273 ret++; 3588 ret++;
3274 } 3589 }
@@ -3338,10 +3653,7 @@ static int validateSesh(reqData *Rd, qhashtbl_t *data, char *name)
3338 } 3653 }
3339 free(t1); 3654 free(t1);
3340 } 3655 }
3341 }
3342 3656
3343 if (0 == ret)
3344 {
3345 if (linky) 3657 if (linky)
3346 { 3658 {
3347 t0 = xmprintf("%s%s", getStrH(tnm, "UUID"), munchie); 3659 t0 = xmprintf("%s%s", getStrH(tnm, "UUID"), munchie);
@@ -3359,6 +3671,10 @@ static int validateSesh(reqData *Rd, qhashtbl_t *data, char *name)
3359 } 3671 }
3360 free(t1); 3672 free(t1);
3361 3673
3674 }
3675
3676 if (0 == ret)
3677 {
3362 if (now.tv_sec > st.st_mtim.tv_sec + idleTimeOut) 3678 if (now.tv_sec > st.st_mtim.tv_sec + idleTimeOut)
3363 { 3679 {
3364 W("Session idled out."); 3680 W("Session idled out.");
@@ -3384,11 +3700,11 @@ W("Validated session linky.");
3384 addStrL(Rd->messages, "NOTE - you wont be able to log onto the grid until your new account has been approved."); 3700 addStrL(Rd->messages, "NOTE - you wont be able to log onto the grid until your new account has been approved.");
3385 Rd->lnk = xzalloc(sizeof(sesh)); 3701 Rd->lnk = xzalloc(sizeof(sesh));
3386 qstrcpy(Rd->lnk->leaf, sizeof(Rd->lnk->leaf), leaf); 3702 qstrcpy(Rd->lnk->leaf, sizeof(Rd->lnk->leaf), leaf);
3387 Rd->chillOut = TRUE;
3388 freeSesh(Rd, linky, FALSE); 3703 freeSesh(Rd, linky, FALSE);
3389 qstrcpy(Rd->lnk->leaf, sizeof(Rd->lnk->leaf), ""); 3704 qstrcpy(Rd->lnk->leaf, sizeof(Rd->lnk->leaf), "");
3390 Rd->func = (pageBuildFunction) loginPage; 3705 Rd->doit = "validate";
3391 Rd->doit = "logout"; 3706 Rd->output = "accountLogin";
3707 Rd->form = "accountLogin";
3392// TODO - we might want to delete their old .lua session as well. Maybe? Don't think we have any suitable codes to find it. 3708// TODO - we might want to delete their old .lua session as well. Maybe? Don't think we have any suitable codes to find it.
3393 } 3709 }
3394 else 3710 else
@@ -3399,13 +3715,16 @@ W("Validated session linky.");
3399 qstrcpy(shs->munchie, sizeof(shs->munchie), munchie); 3715 qstrcpy(shs->munchie, sizeof(shs->munchie), munchie);
3400 qstrcpy(shs->salt, sizeof(shs->salt), tnm->getstr(tnm, "salt", false)); 3716 qstrcpy(shs->salt, sizeof(shs->salt), tnm->getstr(tnm, "salt", false));
3401 qstrcpy(shs->seshID, sizeof(shs->seshID), tnm->getstr(tnm, "seshID", false)); 3717 qstrcpy(shs->seshID, sizeof(shs->seshID), tnm->getstr(tnm, "seshID", false));
3718// TODO - free this somewhere.
3719// shs->name = tnm->getstr(tnm, "name", true);
3720// shs->UUID = tnm->getstr(tnm, "UUID", true);
3402 shs->timeStamp[0].tv_nsec = UTIME_OMIT; 3721 shs->timeStamp[0].tv_nsec = UTIME_OMIT;
3403 shs->timeStamp[0].tv_sec = UTIME_OMIT; 3722 shs->timeStamp[0].tv_sec = UTIME_OMIT;
3404 memcpy(&shs->timeStamp[1], &st.st_mtim, sizeof(struct timespec)); 3723 memcpy(&shs->timeStamp[1], &st.st_mtim, sizeof(struct timespec));
3405 t0 = tnm->getstr(tnm, "linky-hashish", false);
3406 if (NULL != t0)
3407 Rd->stuff->putstr(Rd->stuff, "linky-hashish", t0);
3408 } 3724 }
3725// TODO - free this somewhere.
3726 shs->name = tnm->getstr(tnm, "name", true);
3727 shs->UUID = tnm->getstr(tnm, "UUID", true);
3409 } 3728 }
3410 3729
3411 qhashtbl_obj_t obj; 3730 qhashtbl_obj_t obj;
@@ -3416,9 +3735,9 @@ W("Validated session linky.");
3416 { 3735 {
3417 char *n = obj.name; 3736 char *n = obj.name;
3418 3737
3419 if ((strcmp("salt", n) != 0) && (strcmp("seshID", n) != 0)) 3738 if ((strcmp("salt", n) != 0) && (strcmp("seshID", n) != 0) && (strcmp("UUID", n) != 0))
3420 { 3739 {
3421t("Lua %s = %s", n, (char *) obj.data); 3740t("SessionValidate() Lua read %s = %s", n, (char *) obj.data);
3422 Rd->stuff->putstr(Rd->stuff, obj.name, (char *) obj.data); 3741 Rd->stuff->putstr(Rd->stuff, obj.name, (char *) obj.data);
3423 } 3742 }
3424 } 3743 }
@@ -3438,157 +3757,44 @@ t("Lua %s = %s", n, (char *) obj.data);
3438 return ret; 3757 return ret;
3439} 3758}
3440 3759
3441static int validateAboutMe(reqData *Rd, qhashtbl_t *data, char *name) 3760static void sessionWeb(reqData *Rd, inputForm *iF, inputValue *iV)
3442{ 3761{
3443 int ret = 0; 3762 HTMLhidden(Rd->reply, iV->field->name, iV->value);
3444 char *about = getStrH(data, "aboutMe");
3445
3446 if ((strcmp("confirm", Rd->doit) != 0) && (strcmp("update", Rd->doit) != 0))
3447 return ret;
3448
3449 if ('\0' == about[0])
3450 {
3451 bitch(Rd, "Please fill in the 'About me' section.", "None supplied.");
3452 ret++;
3453 }
3454
3455 badBoy(ret, Rd, data, "aboutMe", about);
3456 return ret;
3457} 3763}
3458 3764
3459 3765/*
3460char *months[] = 3766static int UUIDValidate(reqData *Rd, inputForm *iF, inputValue *iV)
3461{
3462 "january",
3463 "february",
3464 "march",
3465 "april",
3466 "may",
3467 "june",
3468 "july",
3469 "august",
3470 "september",
3471 "october",
3472 "november",
3473 "december"
3474};
3475static int validateDoB(reqData *Rd, qhashtbl_t *data, char *name)
3476{
3477 int ret = 0, i;
3478 char *t;
3479
3480 if ((strcmp("confirm", Rd->doit) != 0) && (strcmp("update", Rd->doit) != 0))
3481 return ret;
3482
3483 t = getStrH(data, "year");
3484 if ((NULL == t) || ('\0' == t[0]))
3485 {
3486 bitch(Rd, "Please supply a year of birth.", "None supplied.");
3487 ret++;
3488 }
3489 else
3490 {
3491 i = atoi(t);
3492 if ((1900 > i) || (i > 2020))
3493 {
3494 bitch(Rd, "Please supply a year of birth.", "Out of range.");
3495 ret++;
3496 }
3497 }
3498
3499 t = getStrH(data, "month");
3500 if ((NULL == t) || ('\0' == t[0]))
3501 {
3502 bitch(Rd, "Please supply a month of birth.", "None supplied.");
3503 ret++;
3504 }
3505 else
3506 {
3507 for (i = 0; i < 12; i++)
3508 {
3509 if (strcmp(months[i], t) == 0)
3510 break;
3511 }
3512 if (12 == i)
3513 {
3514 bitch(Rd, "Please supply a month of birth.", "Out of range");
3515 ret++;
3516 }
3517 }
3518
3519 badBoy(ret, Rd, data, "month", NULL);
3520 badBoy(ret, Rd, data, "year", NULL);
3521 return ret;
3522}
3523
3524static int validateEmail(reqData *Rd, qhashtbl_t *data, char *name)
3525{ 3767{
3526 int ret = 0; 3768 int ret = 0;
3527 char *email = getStrH(data, "email"); 3769 char *UUID = (char *) iV->value;
3528 char *emayl = getStrH(data, "emayl");
3529
3530 if ((strcmp("confirm", Rd->doit) != 0) && (strcmp("update", Rd->doit) != 0))
3531 return ret;
3532 3770
3533 if ((NULL == email) || (NULL == emayl) || ('\0' == email[0]) || ('\0' == emayl[0])) 3771 if (36 != strlen(UUID))
3534 {
3535 bitch(Rd, "Please supply an email address.", "None supplied.");
3536 ret++;
3537 }
3538 else if (strcmp(email, emayl) != 0)
3539 { 3772 {
3540 bitch(Rd, "Email addresses are not the same.", ""); 3773 bitch(Rd, "Internal error.", "UUID isn't long enough.");
3541 ret++; 3774 ret++;
3542 } 3775 }
3543 else if (!qstr_is_email(email)) 3776// TODO - check the characters and dashes as well.
3544 {
3545 bitch(Rd, "Please supply a proper email address.", "Failed qstr_is_email()");
3546 ret++;
3547 }
3548 else
3549 {
3550// TODO - do other email checks - does the domain exist, ..
3551 }
3552 3777
3553 badBoy(ret, Rd, data, "email", NULL); 3778 if (0 == ret)
3554 badBoy(ret, Rd, data, "emayl", NULL); 3779 Rd->stuff->putstr(Rd->stuff, "UUID", UUID);
3555 return ret; 3780 return ret;
3556} 3781}
3557 3782
3558static int validateLegal(reqData *Rd, qhashtbl_t *data, char *name) 3783static void UUIDWeb(reqData *Rd, inputForm *iF, inputValue *iV)
3559{ 3784{
3560 int ret = 0; 3785 HTMLhidden(Rd->reply, iV->field->name, iV->value);
3561 char *t;
3562
3563 t = getStrH(data, "adult");
3564 if ((NULL == t) || (strcmp("on", t) != 0))
3565 {
3566 bitch(Rd, "You must be an adult to enter this site.", "");
3567 ret++;
3568 }
3569 t = getStrH(data, "agree");
3570 if ((NULL == t) || (strcmp("on", t) != 0))
3571 {
3572 bitch(Rd, "You must agree to the Terms & Conditions of Use.", "");
3573 ret++;
3574 }
3575
3576 badBoy(ret, Rd, data, "adult", NULL);
3577 badBoy(ret, Rd, data, "agree", NULL);
3578 return ret;
3579} 3786}
3787*/
3580 3788
3581static int validateName(reqData *Rd, qhashtbl_t *data, char *nm) 3789static int nameValidate(reqData *Rd, inputForm *iF, inputValue *iV)
3582{ 3790{
3583 boolean login = strcmp("login", Rd->doit) == 0;
3584 int ret = 0; 3791 int ret = 0;
3585 unsigned char *name; // We have to be unsigned coz of isalnum(). 3792 unsigned char *name; // We have to be unsigned coz of isalnum().
3586 char *where = NULL; 3793 char *where = NULL;
3587 3794
3588 if ((strcmp("cancel", Rd->doit) == 0) || (strcmp("logout", Rd->doit) == 0)) 3795//d("nameValidate %s", iV->field->name);
3589 return ret;
3590 3796
3591 name = data->getstr(data, "name", true); 3797 name = xstrdup(iV->value);
3592 3798
3593 if ((NULL == name) || ('\0' == name[0])) 3799 if ((NULL == name) || ('\0' == name[0]))
3594 { 3800 {
@@ -3648,350 +3854,469 @@ static int validateName(reqData *Rd, qhashtbl_t *data, char *nm)
3648 ret++; 3854 ret++;
3649 break; 3855 break;
3650 } 3856 }
3857// TODO - compare first, last, and fullname against god names, complain and fail if there's a match.
3651 } 3858 }
3652 } 3859 }
3653 3860
3654 struct stat st; 3861 if (0 == ret)
3655 struct timespec now;
3656 qhashtbl_t *tnm = qhashtbl(0, 0);
3657 int rt = 0;
3658
3659 if (s) {s--; *s = '_'; s++;}
3660 where = xmprintf("%s/users/%s.lua", scData, name);
3661 rt = LuaToHash(Rd, where, "user", tnm, ret, &st, &now, "user");
3662 if (s) {s--; *s = '\0'; s++;}
3663 free(where);
3664
3665 static dbRequest *acnts = NULL;
3666 if (NULL == acnts)
3667 { 3862 {
3668 static char *szi[] = {"FirstName", "LastName", NULL}; 3863 Rd->stuff->putstr(Rd->stuff, "firstName", name);
3669 static char *szo[] = {NULL}; 3864 Rd->stuff->putstr(Rd->stuff, "lastName", s);
3670 acnts = xzalloc(sizeof(dbRequest)); 3865 if (s) {s--; *s = ' '; s++;}
3671 acnts->db = Rd->db; 3866 s = NULL;
3672 acnts->table = "UserAccounts"; 3867 Rd->stuff->putstr(Rd->stuff, "name", name);
3673 acnts->inParams = szi; 3868 Rd->shs.name = xstrdup(name);
3674 acnts->outParams = szo;
3675 acnts->where = "FirstName=? and LastName=?";
3676 dbRequests->addfirst(dbRequests, acnts, sizeof(*acnts));
3677 } 3869 }
3678 dbDoSomething(acnts, FALSE, name, s);
3679 rowData *rows = acnts->rows;
3680 int c = 0;
3681
3682 if (rows)
3683 c = rows->rows->size(rows->rows);
3684
3685 if (login)
3686 {
3687 if ((1 != c) && (rt))
3688 {
3689 if (rt)
3690 {
3691 W("Could not read user Lua file.");
3692 ret += rt;
3693 }
3694 if (0 == c)
3695 {
3696 W("No UserAccounts record with that name.");
3697 ret++;
3698 }
3699 else
3700 {
3701 W("More than one UserAccounts record with that name.");
3702 ret++;
3703 }
3704 bitch(Rd, "Login failed.", "Could not find user record.");
3705 }
3706 else
3707 {
3708 if (1 == c)
3709 dbPull(Rd, "UserAccounts", rows);
3710 else
3711 {
3712 Rd->database->putstr(Rd->database, "UserAccounts.FirstName", name);
3713 Rd->database->putstr(Rd->database, "UserAccounts.LastName", s);
3714 Rd->database->putstr(Rd->database, "UserAccounts.Email", getStrH(tnm, "email"));
3715 Rd->database->putstr(Rd->database, "UserAccounts.Created", getStrH(tnm, "created"));
3716 Rd->database->putstr(Rd->database, "UserAccounts.PrincipleID", getStrH(tnm, "UUID"));
3717 Rd->database->putstr(Rd->database, "UserAccounts.UserLevel", getStrH(tnm, "level"));
3718 Rd->database->putstr(Rd->database, "UserAccounts.UserFlags", getStrH(tnm, "flags"));
3719 Rd->database->putstr(Rd->database, "UserAccounts.UserTitle", getStrH(tnm, "title"));
3720 Rd->database->putstr(Rd->database, "UserAccounts.active", getStrH(tnm, "active"));
3721 Rd->database->putstr(Rd->database, "auth.passwordSalt", getStrH(tnm, "passwordSalt"));
3722 Rd->database->putstr(Rd->database, "auth.passwordHash", getStrH(tnm, "passwordHash"));
3723 tnm->free(tnm);
3724 }
3725 Rd->stuff->putstr(Rd->stuff, "UUID", getStrH(Rd->database, "UserAccounts.PrincipalID"));
3726 Rd->stuff->putstr(Rd->stuff, "level", getStrH(Rd->database, "UserAccounts.Userlevel"));
3727 if (s) {s--; *s = ' '; s++;}
3728 Rd->stuff->putstr(Rd->stuff, "name", name);
3729 if (s) {s--; *s = '\0'; s++;}
3730 }
3731 }
3732 else if (strcmp("create", Rd->doit) == 0)
3733 {
3734 if ((0 != c) && (!rt))
3735 {
3736 bitch(Rd, "Pick a different name.", "An existing Lua user file or UserAccounts record matched that name.");
3737 ret++;
3738 }
3739 else
3740 {
3741// TODO - compare first, last, and fullname against god names, complain and fail if there's a match.
3742 // Generate a UUID, check it isn't already being used.
3743 char uuid[37];
3744 uuid_t binuuid;
3745 my_ulonglong users = 0;
3746
3747 do // UserAccounts.PrincipalID is a unique primary index anyway, but we want the user creation process to be a little on the slow side.
3748 {
3749 struct stat st;
3750
3751 uuid_generate_random(binuuid);
3752 uuid_unparse_lower(binuuid, uuid);
3753 // Try Lua user file.
3754 where = xmprintf("%s/users/%s.lua", scData, uuid);
3755 c = stat(where, &st);
3756 if (c)
3757 users = 1;
3758 free(where);
3759 // Try database.
3760 where = xmprintf("UserAccounts.PrincipalID = '%s'", uuid);
3761 D("Trying new UUID %s.", where);
3762 users = dbCount(Rd->db, "UserAccounts", where);
3763 free(where);
3764 } while (users != 0);
3765// TODO - perhaps create a place holder UserAccounts record? Then we'll have to deal with deleting them later.
3766 Rd->stuff->putstr(Rd->stuff, "UUID", xstrdup(uuid));
3767 Rd->stuff->putstr(Rd->stuff, "level", xstrdup("-200"));
3768 Rd->database->putstr(Rd->database, "UserAccounts.PrincipalID",xstrdup(uuid));
3769 Rd->database->putstr(Rd->database, "UserAccounts.Userlevel", xstrdup("-200"));
3770 Rd->database->putstr(Rd->database, "UserAccounts.firstName", xstrdup(name));
3771 Rd->database->putstr(Rd->database, "UserAccounts.lastName", xstrdup(s));
3772 if (s) {s--; *s = ' '; s++;}
3773 Rd->stuff->putstr(Rd->stuff, "name", xstrdup(name));
3774 }
3775 }
3776 free(rows->fieldNames);
3777 rows->rows->free(rows->rows);
3778 free(rows);
3779 tnm->free(tnm);
3780 if (s) {s--; *s = ' '; s++;} 3870 if (s) {s--; *s = ' '; s++;}
3781 } 3871 }
3782 } 3872 }
3783 free(name); 3873 free(name);
3784 3874
3785 badBoy(ret, Rd, data, "name", NULL);
3786 return ret; 3875 return ret;
3787} 3876}
3788 3877
3789static int validatePassword(reqData *Rd, qhashtbl_t *data, char *name) 3878static void nameWeb(reqData *Rd, inputForm *oF, inputValue *oV)
3879{
3880 if (oV->field->flags & FLD_HIDDEN)
3881 HTMLhidden(Rd->reply, oV->field->name, oV->value);
3882 else
3883 HTMLtext(Rd->reply, "text", oV->field->title, oV->field->name, oV->value, oV->field->viewLength, oV->field->maxLength, oV->field->flags & FLD_REQUIRED);
3884}
3885
3886
3887static int passwordValidate(reqData *Rd, inputForm *iF, inputValue *iV)
3790{ 3888{
3791 boolean login = strcmp("login", Rd->doit) == 0;
3792 boolean create = strcmp("create", Rd->doit) == 0;
3793 int ret = 0; 3889 int ret = 0;
3794 char *password = getStrH(data, "password"); 3890 char *password = (char *) iV->value, *salt = getStrH(Rd->stuff, "passSalt"), *hash = getStrH(Rd->stuff, "passHash");
3795 char *psswrdH = getStrH(Rd->stuff, "passwordHash"); 3891
3796 char *psswrdS = getStrH(Rd->stuff, "passwordSalt"); 3892 if ((NULL == password) || ('\0' == password[0]))
3893 {
3894 bitch(Rd, "Please supply a password.", "Password empty or missing.");
3895 ret++;
3896 }
3897 else if (('\0' != salt[0]) && ('\0' != hash[0]) && (strcmp("psswrd", iV->field->name) == 0))
3898 {
3899 D("Comparing passwords. %s %s %s", password, salt, hash);
3900 char *h = checkSLOSpassword(Rd, salt, password, hash, "Passwords are not the same.");
3901
3902 if (NULL == h)
3903 ret++;
3904 else
3905 free(h);
3906 }
3907
3908// TODO - once the password is validated, store it as the salt and hash.
3909// If it's an existing account, compare it? Or do that later?
3910 if (0 == ret)
3911 Rd->stuff->putstr(Rd->stuff, "password", password);
3912
3913 return ret;
3914}
3915
3916static void passwordWeb(reqData *Rd, inputForm *oF, inputValue *oV)
3917{
3918 HTMLtext(Rd->reply, "password", oV->field->title, oV->field->name, "", oV->field->viewLength, oV->field->maxLength, oV->field->flags & FLD_REQUIRED);
3919 Rd->reply->addstr(Rd->reply, "<p>While viewers will usually remember your name and password for you, you'll need to remember it for this web site to. &nbsp; "
3920 "I highly recommend using a password manager. &nbsp; KeePass and it's variations is a great password manager.</p>\n");
3921}
3797 3922
3798 if (login) 3923static int emailValidate(reqData *Rd, inputForm *iF, inputValue *iV)
3924{
3925// inputField **group = iV->field->group;
3926 int ret = 0, i;
3927 boolean notSame = FALSE;
3928
3929 i = iV->index;
3930 if (2 == i)
3799 { 3931 {
3800 char *UUID = getStrH(Rd->database, "UserAccounts.PrincipalID"); 3932 char *email = (char *) iV->value;
3801 static dbRequest *auth = NULL; 3933 char *emayl = (char *) (iV + 1)->value;
3802 3934
3803 if ('\0' == UUID[0]) // If the name validation didn't find us a UUID, then this user doesn't exist, so can't log them on. 3935 if ((NULL == email) || (NULL == emayl) || ('\0' == email[0]) || ('\0' == emayl[0]))
3804 return 1; 3936 {
3805 if (NULL == auth) 3937 bitch(Rd, "Please supply an email address.", "None supplied.");
3938 ret++;
3939 }
3940 else if (strcmp(email, emayl) != 0)
3941 {
3942 bitch(Rd, "Email addresses are not the same.", "");
3943 ret++;
3944 notSame = TRUE;
3945 }
3946 else if (!qstr_is_email(email))
3806 { 3947 {
3807 static char *szi[] = {"UUID", NULL}; 3948 bitch(Rd, "Please supply a proper email address.", "Failed qstr_is_email()");
3808 static char *szo[] = {"passwordSalt", "passwordHash", NULL}; 3949 ret++;
3809 auth = xzalloc(sizeof(dbRequest));
3810 auth->db = Rd->db;
3811 auth->table = "auth";
3812 auth->inParams = szi;
3813 auth->outParams = szo;
3814 auth->where = "UUID=?";
3815 dbRequests->addfirst(dbRequests, auth, sizeof(*auth));
3816 } 3950 }
3817 dbDoSomething(auth, FALSE, UUID); 3951 else
3818 rowData *rows = auth->rows;
3819 if (rows)
3820 { 3952 {
3821 int i = rows->rows->size(rows->rows); 3953// TODO - do other email checks - does the domain exist, ..
3954 }
3822 3955
3823 if (i != 1) 3956 if ((NULL != email) && (NULL != emayl))
3824 { 3957 {
3825 bitch(Rd, "Login failed.", "Wrong number of auth records."); 3958 char *t0 = qurl_encode(email, strlen(email));
3826 ret++;
3827 }
3828 else
3829 {
3830 qhashtbl_t *me = rows->rows->popfirst(rows->rows, NULL);
3831 unsigned char md5hash[16];
3832 3959
3833 if (!qhashmd5((void *) password, strlen(password), md5hash)) 3960 // In theory it's the correct thing to do to NOT load email into stuff on failure,
3834 { 3961 // In practice, that means it wont show the old email and emayl in the create page when they don't match.
3835 bitch(Rd, "Login failed, internal error.", "Login - qhashmd5(password) failed."); 3962 if ((0 == ret) || notSame)
3836 ret++; 3963 Rd->stuff->putstrf(Rd->stuff, "email", "%s", t0);
3837 } 3964 free(t0);
3838 else 3965 }
3839 { 3966 if ((NULL != email) && (NULL != emayl))
3840 Rd->stuff->putstr(Rd->stuff, "passwordSalt", getStrH(me, "passwordSalt")); 3967 {
3968 char *t1 = qurl_encode(emayl, strlen(emayl));
3841 3969
3842 char *md5ascii = qhex_encode(md5hash, 16); 3970 Rd->stuff->putstrf(Rd->stuff, "emayl", "%s", t1);
3843 char *where = xmprintf("%s:%s", md5ascii, getStrH(me, "passwordSalt")); 3971 free(t1);
3844 if (!qhashmd5((void *) where, strlen(where), md5hash))
3845 {
3846 bitch(Rd, "Login failed, internal error.", "Login - qhashmd5(passwordSalt) failed.");
3847 ret++;
3848 }
3849 else
3850 {
3851 free(md5ascii);
3852 md5ascii = qhex_encode(md5hash, 16);
3853 if (strcmp(md5ascii, getStrH(me, "passwordHash")) != 0)
3854 {
3855 bitch(Rd, "Login failed.", "passwordHash doesn't match");
3856 ret++;
3857 }
3858 Rd->stuff->putstr(Rd->stuff, "passwordHash", md5ascii);
3859 }
3860 free(md5ascii);
3861 free(where);
3862 }
3863 free(me);
3864 }
3865 free(rows->fieldNames);
3866 rows->rows->free(rows->rows);
3867 free(rows);
3868 } 3972 }
3869 } 3973 }
3870 else if (create) 3974
3975 return ret;
3976}
3977static void emailWeb(reqData *Rd, inputForm *oF, inputValue *oV)
3978{
3979 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);
3980}
3981
3982
3983char *months[] =
3984{
3985 "january",
3986 "february",
3987 "march",
3988 "april",
3989 "may",
3990 "june",
3991 "july",
3992 "august",
3993 "september",
3994 "october",
3995 "november",
3996 "december"
3997};
3998static int DoBValidate(reqData *Rd, inputForm *iF, inputValue *iV)
3999{
4000 int ret = 0, i;
4001 char *t0, *t1;
4002// inputField **group = iV->field->group;
4003
4004 i = iV->index;
4005 if (2 == i)
3871 { 4006 {
3872 if ((NULL == password) || ('\0' == password[0])) 4007 t0 = (char *) iV->value;
4008 if ((NULL == t0) || ('\0' == t0[0]))
3873 { 4009 {
3874 bitch(Rd, "Please supply a password.", "Password empty or missing."); 4010 bitch(Rd, "Please supply a year of birth.", "None supplied.");
3875 ret++; 4011 ret++;
3876 } 4012 }
3877 else 4013 else
3878 { 4014 {
3879 // https://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid 4015 i = atoi(t0);
3880 // Has a great discussion. 4016 if ((1900 > i) || (i > 2020))
3881 // http://tools.ietf.org/html/rfc4122
3882 // https://en.wikipedia.org/wiki/Universally_unique_identifier
3883 // Useful stuff about versions and variants, a quick look says OpenSim is using version 4 (random), variant 1.
3884 // Note versions 3 and 5 are hash based, like I wanted for SledjHamr. B-)
3885 // https://news.ycombinator.com/item?id=14523523 is a bad blog post with a really good and lengthy comments section, concerning use as database keys.
3886 // Better off using the 16 byte / 128 bit integer version of UUIDs for database keys, but naturally OpenSim uses char(36) / 304 bit, and sometimes varchar(255).
3887
3888 // Calculate passwordSalt and passwordHash. From Opensim -
3889 // passwordSalt = Util.Md5Hash(UUID.Random().ToString())
3890 // passwdHash = Util.Md5Hash(Util.Md5Hash(password) + ":" + passwordSalt)
3891 unsigned char *md5hash = xzalloc(17);
3892 char *salt, *hash;
3893 char uuid[37];
3894 uuid_t binuuid;
3895
3896 uuid_generate_random(binuuid);
3897 uuid_unparse_lower(binuuid, uuid);
3898 if (!qhashmd5((void *) uuid, strlen(uuid), md5hash))
3899 { 4017 {
3900 bitch(Rd, "Internal session error.", "Create - qhashmd5(new uuid) failed."); 4018 bitch(Rd, "Please supply a year of birth.", "Out of range.");
3901 ret++; 4019 ret++;
3902 } 4020 }
3903 else
3904 {
3905 salt = qhex_encode(md5hash, 16);
3906 Rd->stuff->putstr(Rd->stuff, "passwordSalt", salt);
3907 if (!qhashmd5((void *) password, strlen(password), md5hash))
3908 {
3909 bitch(Rd, "Internal session error.", "Create - qhashmd5(password) failed.");
3910 ret++;
3911 }
3912 else
3913 {
3914 free(salt);
3915 salt = qhex_encode(md5hash, 16);
3916 hash = xmprintf("%s:%s", salt, getStrH(Rd->stuff, "passwordSalt"));
3917 if (!qhashmd5((void *) hash, strlen(hash), md5hash))
3918 {
3919 bitch(Rd, "Internal session error.", "Create - qhashmd5(passwordSalt) failed.");
3920 ret++;
3921 }
3922 else
3923 {
3924 free(hash);
3925 hash = qhex_encode(md5hash, 16);
3926 Rd->stuff->putstr(Rd->stuff, "passwordHash", hash);
3927 Rd->chillOut = TRUE;
3928 }
3929 free(hash);
3930 free(salt);
3931 }
3932 }
3933 } 4021 }
3934 } 4022 t1 = (char *) (iV + 1)->value;
3935 else if (strcmp("confirm", Rd->doit) == 0) 4023 if ((NULL == t1) || ('\0' == t1[0]))
3936 {
3937 if ((NULL == password) || ('\0' == password[0]))
3938 { 4024 {
3939 bitch(Rd, "Please supply a password.", "Need two passwords."); 4025 bitch(Rd, "Please supply a month of birth.", "None supplied.");
3940 ret++; 4026 ret++;
3941 } 4027 }
3942 else 4028 else
3943 { 4029 {
3944 unsigned char *md5hash = xzalloc(17); 4030 for (i = 0; i < 12; i++)
3945 char *pswd, *hash;
3946 char *Osalt = getStrH(Rd->stuff, "passwordSalt"), *Ohash = getStrH(Rd->stuff, "passwordHash");
3947
3948 if (!qhashmd5((void *) password, strlen(password), md5hash))
3949 { 4031 {
3950 bitch(Rd, "Internal session error.", "Confirm - qhashmd5(password) failed."); 4032 if (strcmp(months[i], t1) == 0)
3951 ret++; 4033 break;
3952 } 4034 }
3953 else 4035 if (12 == i)
3954 { 4036 {
3955 pswd = qhex_encode(md5hash, 16); 4037 bitch(Rd, "Please supply a month of birth.", "Out of range");
3956 hash = xmprintf("%s:%s", pswd, Osalt); 4038 ret++;
3957 free(pswd);
3958 if (!qhashmd5((void *) hash, strlen(hash), md5hash))
3959 {
3960 bitch(Rd, "Internal session error.", "Confirm - qhashmd5(passwordSalt) failed.");
3961 ret++;
3962 }
3963 else
3964 {
3965 free(hash);
3966 hash = qhex_encode(md5hash, 16);
3967 if (strcmp(hash, Ohash) != 0)
3968 {
3969 bitch(Rd, "Passwords are not the same.", "");
3970 ret++;
3971 }
3972 free(hash);
3973 }
3974 } 4039 }
3975 } 4040 }
4041
4042 if (0 == ret)
4043 {
4044 Rd->stuff->putstr(Rd->stuff, "year", t0);
4045 Rd->stuff->putstr(Rd->stuff, "month", t1);
4046 Rd->stuff->putstrf(Rd->stuff, "DoB", "%s %s", t0, t1);
4047 }
3976 } 4048 }
3977 4049
3978// TODO - try to find code for dealing with security enclaves, encrypted memory, and such. 4050 return ret;
3979// NOTE - these get filtered through what ever web server is being used, and might leak there. 4051}
3980 OPENSSL_cleanse(password, strlen(password)); 4052static void DoByWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4053{
4054 char *tmp = xmalloc(16), *t;
4055 int i, d;
3981 4056
3982 badBoy(ret, Rd, data, "auth.passwordSalt", NULL); 4057 Rd->reply->addstr(Rd->reply, "<label>Date of birth :<table><tr><td>\n");
4058 HTMLselect(Rd->reply, NULL, oV->field->name);
4059 t = getStrH(Rd->stuff, "year");
4060 if (NULL == t)
4061 d = -1;
4062 else
4063 d = atoi(t);
4064 HTMLoption(Rd->reply, "", FALSE);
4065 for (i = 1900; i <= 2020; i++)
4066 {
4067 boolean sel = FALSE;
3983 4068
3984 return ret; 4069 if (i == d)
4070 sel = TRUE;
4071 sprintf(tmp, "%d", i);
4072 HTMLoption(Rd->reply, tmp, sel);
4073 }
4074 free(tmp);
4075 HTMLselectEndNo(Rd->reply);
3985} 4076}
4077static void DoBmWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4078{
4079 char *t;
4080 int i, d;
3986 4081
4082 Rd->reply->addstr(Rd->reply, "</td><td>\n");
4083 HTMLselect(Rd->reply, NULL, oV->field->name);
4084 t = getStrH(Rd->stuff, "month");
4085 HTMLoption(Rd->reply, "", FALSE);
4086 for (i = 0; i <= 11; i++)
4087 {
4088 boolean sel = FALSE;
3987 4089
3988static int validateUUID(reqData *Rd, qhashtbl_t *data, char *name) 4090 if ((NULL != t) && (strcmp(t, months[i]) == 0))
4091 sel = TRUE;
4092 HTMLoption(Rd->reply, months[i], sel);
4093 }
4094 HTMLselectEndNo(Rd->reply);
4095 Rd->reply->addstr(Rd->reply, "</td></tr></table></label>\n");
4096}
4097static void DoBWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4098{
4099}
4100
4101static int legalValidate(reqData *Rd, inputForm *iF, inputValue *iV)
3989{ 4102{
3990 int ret = 0, i; 4103 int ret = 0, i;
3991 char uuid[37], *t; 4104 char *t;
4105 inputField **group = iV->field->group;
4106
4107 i = iV->index;
4108 if (2 == i)
4109 {
4110 t = (char *) iV->value;
4111 if ((NULL == t) || (strcmp("on", t) != 0))
4112 {
4113 bitch(Rd, "You must be an adult to enter this site.", "");
4114 ret++;
4115 }
4116 else
4117 Rd->stuff->putstr(Rd->stuff, "adult", t);
4118 t = (char *) (iV + 1)->value;
4119 if ((NULL == t) || (strcmp("on", t) != 0))
4120 {
4121 bitch(Rd, "You must agree to the Terms & Conditions of Use.", "");
4122 ret++;
4123 }
4124 else
4125 Rd->stuff->putstr(Rd->stuff, "agree", t);
4126 }
4127
4128 return ret;
4129}
4130static void adultWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4131{
4132 HTMLcheckBox(Rd->reply, oV->field->name, oV->field->title, !strcmp("on", getStrH(Rd->body, "adult")), oV->field->flags & FLD_REQUIRED);
4133}
4134static void agreeWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4135{
4136 HTMLcheckBox(Rd->reply, oV->field->name, oV->field->title, !strcmp("on", getStrH(Rd->body, "agree")), oV->field->flags & FLD_REQUIRED);
4137}
4138static void legalWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4139{
4140}
4141static void ToSWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4142{
4143 Rd->reply->addstrf(Rd->reply, "<h2>Terms of Service</h2><pre>%s</pre>\n", getStrH(Rd->configs, "ToS"));
4144}
4145
4146static int aboutMeValidate(reqData *Rd, inputForm *oF, inputValue *oV)
4147{
4148 int ret = 0;
4149 char *about = (char *) oV->value;
4150
4151 if ((NULL == about) || ('\0' == about[0]))
4152 {
4153 bitch(Rd, "Please fill in the 'About me' section.", "None supplied.");
4154 ret++;
4155 }
4156
4157 if ((0 == ret) && (NULL != about))
4158 {
4159 char *t = qurl_encode(about, strlen(about));
4160 Rd->stuff->putstr(Rd->stuff, "aboutMe", t);
4161 free(t);
4162 }
4163
4164 return ret;
4165}
4166
4167static void aboutMeWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4168{
4169 // For maxlength - the MySQL database field is type text, which has a max length of 64 Kilobytes byets, but characters might take up 1 - 4 bytes, and maxlength is in characters.
4170 // For rows and cols, seems a bit broken, I ask for 5/42, I get 6,36. In world it seems to be 7,46
4171// TODO - check against the limit for in world profiles, coz this will become that.
4172// TODO - validate aboutMe, it should not be empty, and should not be longer than 64 kilobytes.
4173 HTMLtextArea(Rd->reply, oV->field->name, oV->field->title, 7, oV->field->viewLength, 4, oV->field->maxLength, "Describe yourself here.", "off", "true", "soft", oV->value, FALSE, FALSE);
4174}
4175
4176static void accountWebHeaders(reqData *Rd, inputForm *oF, char *name)
4177{
4178 char *linky = checkLinky(Rd);
4179
4180 HTMLheader(Rd->reply, "<!--#echo var=\"grid\" --> account manager");
4181 Rd->reply->addstrf(Rd->reply, "<h1><!--#echo var=\"grid\" --> account manager</h1>\n");
4182 if (NULL != name)
4183 {
4184 Rd->reply->addstrf(Rd->reply, "<h2><!--#echo var=\"grid\" --> account for %s</h2>\n", name);
4185 Rd->reply->addstr(Rd->reply, linky);
4186 }
4187 free(linky);
4188 if (0 != Rd->errors->size(Rd->messages))
4189 HTMLlist(Rd->reply, "messages -", Rd->messages);
4190 if (NULL != oF->help)
4191 Rd->reply->addstrf(Rd->reply, "<p>%s</p>\n", oF->help);
4192 HTMLform(Rd->reply, "", Rd->shs.munchie);
4193 HTMLhidden(Rd->reply, "form", oF->name);
4194}
4195
4196static void accountWebFields(reqData *Rd, inputForm *oF, inputValue *oV)
4197{
4198 int count = oF->fields->size(oF->fields), i;
4199
4200 for (i = 0; i < count; i++)
4201 {
4202 if (NULL != oV[i].field->web)
4203 oV[i].field->web(Rd, oF, &oV[i]);
4204 if ((NULL != oV[i].field->help) && ('\0' != oV[i].field->help[0]))
4205 Rd->reply->addstrf(Rd->reply, "<p>%s</p>\n", oV[i].field->help);
4206//d("accountWebFeilds(%s, %s)", oF->name, oV[i].field->name);
4207 }
4208}
4209
4210static void accountWebSubs(reqData *Rd, inputForm *oF)
4211{
4212 qhashtbl_obj_t obj;
4213
4214 Rd->reply->addstrf(Rd->reply, "<input type='submit' disabled style='display: none' aria-hidden='true' />\n"); // Stop Enter key on text fields triggering the first submit button.
4215 memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
4216 oF->subs->lock(oF->subs);
4217 while(oF->subs->getnext(oF->subs, &obj, false) == true)
4218 {
4219 inputSub *sub = (inputSub *) obj.data;
4220 if ('\0' != sub->title[0])
4221 HTMLbutton(Rd->reply, sub->title);
4222//d("accountWebSubs(%s, %s '%s')", oF->name, sub->name, sub->title);
4223 }
4224 oF->subs->unlock(oF->subs);
4225}
4226
4227static void accountWebFooter(reqData *Rd, inputForm *oF)
4228{
4229 if (0 != Rd->errors->size(Rd->errors))
4230 HTMLlist(Rd->reply, "errors -", Rd->errors);
4231 HTMLformEnd(Rd->reply);
4232 HTMLfooter(Rd->reply);
4233}
4234
4235static void accountAddWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4236{
4237 char *name = getStrH(Rd->stuff, "name");
4238
4239 accountWebHeaders(Rd, oF, name);
4240 accountWebFields(Rd, oF, oV);
4241 accountWebSubs(Rd, oF);
4242 accountWebFooter(Rd, oF);
4243}
4244
4245static void accountLoginWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4246{
4247 char *name = getStrH(Rd->stuff, "name");
4248
4249 Rd->shs.UUID = NULL;
4250 accountWebHeaders(Rd, oF, NULL);
4251 accountWebFields(Rd, oF, oV);
4252 accountWebSubs(Rd, oF);
4253 accountWebFooter(Rd, oF);
4254}
4255
4256static void accountViewWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4257{
4258 time_t crtd = atol(getStrH(Rd->database, "UserAccounts.Created"));
4259
4260 accountWebHeaders(Rd, oF, getStrH(Rd->stuff, "name"));
4261 accountWebFields(Rd, oF, oV);
4262// TODO - still need to encode < > as &lt; u&gt; for email and about.
4263// TODO - dammit, qurl_decode returns the string length, and decodes the string in place.
4264 Rd->reply->addstrf(Rd->reply, "<p><font size='5'><span style='font-size: x-large'><b>Title / level :</b></span></font> %s / %d</p>", getLevel(Rd), Rd->shs.level);
4265 Rd->reply->addstrf(Rd->reply, "<p><font size='5'><span style='font-size: x-large'><b>Date of birth :</b></span></font> %s</p>", getStrH(Rd->database, "Lua.DoB"));
4266 Rd->reply->addstrf(Rd->reply, "<p><font size='5'><span style='font-size: x-large'><b>Created :</b></span></font> %s</p>", ctime(&crtd));
4267 Rd->reply->addstrf(Rd->reply, "<p><font size='5'><span style='font-size: x-large'><b>Email :</b></span></font> %s</p>", displayPrep(getStrH(Rd->stuff, "email")));
4268 Rd->reply->addstrf(Rd->reply, "<p><font size='5'><span style='font-size: x-large'><b>UUID :</b></span></font> %s</p>", Rd->shs.UUID);
4269// Rd->reply->addstrf(Rd->reply, "<p><font size='5'><span style='font-size: x-large'><b>About :</b></span></font> </p>"
4270// "<textarea readonly >%s</textarea>", qurl_decode(getStrH(Rd->database, "Lua.aboutMe")));
4271 HTMLtextArea(Rd->reply, "aboutMe", "About", 7, 50, 4, 16384, "", "off", "true", "soft", displayPrep(getStrH(Rd->database, "Lua.aboutMe")), FALSE, TRUE);
4272 accountWebSubs(Rd, oF);
4273 accountWebFooter(Rd, oF);
4274}
4275
4276static void accountEditWeb(reqData *Rd, inputForm *oF, inputValue *oV)
4277{
4278 char *name = getStrH(Rd->stuff, "name");
4279
4280 accountWebHeaders(Rd, oF, name);
4281 accountWebFields(Rd, oF, oV);
4282 HTMLtext(Rd->reply, "password", "Old password", "password", "", 16, 0, FALSE);
4283 Rd->reply->addstr(Rd->reply, "<p>Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters.</p>\n");
4284//// HTMLtext(Rd->reply, "title", "text", "title", getStrH(Rh->stuff, "title"), 16, 64, TRUE);
4285
4286 qlisttbl_obj_t obj;
4287 char *lvl = getLevel(Rd);
4288
4289 HTMLselect(Rd->reply, "level", "level");
4290 memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
4291 accountLevels->lock(accountLevels);
4292 while(accountLevels->getnext(accountLevels, &obj, NULL, false) == true)
4293 {
4294 boolean is = false;
4295
4296 if (strcmp(lvl, (char *) obj.data) == 0)
4297 is = true;
4298 HTMLoption(Rd->reply, (char *) obj.data, is);
4299 }
4300 accountLevels->unlock(accountLevels);
4301 HTMLselectEnd(Rd->reply);
4302
4303 accountWebSubs(Rd, oF);
4304 accountWebFooter(Rd, oF);
4305}
4306
4307
4308static int accountRead(reqData *Rd, inputForm *iF, inputValue *iV)
4309{
4310 int ret = 0, rt = -1;
4311 struct stat st;
4312 struct timespec now;
4313 qhashtbl_t *tnm = qhashtbl(0, 0);
4314 char *uuid, *first, *last;
4315 uuid_t binuuid;
3992 rowData *rows = NULL; 4316 rowData *rows = NULL;
3993 static dbRequest *uuids = NULL;
3994 4317
4318 // Setup the database stuff.
4319 static dbRequest *uuids = NULL;
3995 if (NULL == uuids) 4320 if (NULL == uuids)
3996 { 4321 {
3997 static char *szi[] = {"PrincipalID", NULL}; 4322 static char *szi[] = {"PrincipalID", NULL};
@@ -4004,249 +4329,584 @@ static int validateUUID(reqData *Rd, qhashtbl_t *data, char *name)
4004 uuids->where = "PrincipalID=?"; 4329 uuids->where = "PrincipalID=?";
4005 dbRequests->addfirst(dbRequests, uuids, sizeof(*uuids)); 4330 dbRequests->addfirst(dbRequests, uuids, sizeof(*uuids));
4006 } 4331 }
4332 static dbRequest *acnts = NULL;
4333 if (NULL == acnts)
4334 {
4335 static char *szi[] = {"FirstName", "LastName", NULL};
4336 static char *szo[] = {NULL};
4337 acnts = xzalloc(sizeof(dbRequest));
4338 acnts->db = Rd->db;
4339 acnts->table = "UserAccounts";
4340 acnts->inParams = szi;
4341 acnts->outParams = szo;
4342 acnts->where = "FirstName=? and LastName=?";
4343 dbRequests->addfirst(dbRequests, acnts, sizeof(*acnts));
4344 }
4345 static dbRequest *auth = NULL;
4346 if (NULL == auth)
4347 {
4348 static char *szi[] = {"UUID", NULL};
4349 static char *szo[] = {"passwordSalt", "passwordHash", NULL};
4350 auth = xzalloc(sizeof(dbRequest));
4351 auth->db = Rd->db;
4352 auth->table = "auth";
4353 auth->inParams = szi;
4354 auth->outParams = szo;
4355 auth->where = "UUID=?";
4356 dbRequests->addfirst(dbRequests, auth, sizeof(*auth));
4357 }
4007 4358
4008 if ((strcmp("cancel", Rd->doit) == 0) || (strcmp("logout", Rd->doit) == 0)) 4359 uuid = Rd->shs.UUID; first = getStrH(Rd->stuff, "firstName"); last = getStrH(Rd->stuff, "lastName");
4009 return ret; 4360d("accountRead() UUID %s, name %s %s", uuid, first, last);
4361 uuid_clear(binuuid);
4362 if ((NULL != uuid) && ('\0' != uuid[0]))
4363 uuid_parse(uuid, binuuid);
4364 if ((NULL != uuid) && ('\0' != uuid[0]) && (!uuid_is_null(binuuid)))
4365 {
4366 char *where = xmprintf("%s/users/%s.lua", scData, uuid);
4367 rt = LuaToHash(Rd, where, "user", tnm, ret, &st, &now, "user");
4010 4368
4011 uuid[0] = '\0'; 4369 free(where);
4012 if (strcmp("create", Rd->doit) == 0) 4370 dbDoSomething(uuids, FALSE, uuid);
4371 rows = uuids->rows;
4372 }
4373 else
4013 { 4374 {
4014 // Generate a UUID, check it isn't already being used, and totally ignore whatever UUID is in body.
4015 uuid_t binuuid;
4016 4375
4017 do // UserAccounts.PrincipalID is a unique primary index anyway, but we want the user creation process to be a little on the slow side. 4376 if ('\0' != first[0])
4018 { 4377 {
4019 uuid_generate_random(binuuid); 4378 char *where = xmprintf("%s/users/%s_%s.lua", scData, first, last);
4020 uuid_unparse_lower(binuuid, uuid); 4379 rt = LuaToHash(Rd, where, "user", tnm, ret, &st, &now, "user");
4021 4380
4022 d("Trying new UUID %s.", uuid); 4381 free(where);
4023// TODO - check the Lua user files as well. 4382 dbDoSomething(acnts, FALSE, first, last);
4024 dbDoSomething(uuids, FALSE, uuid); 4383 rows = acnts->rows;
4025 rows = uuids->rows; 4384 }
4026 i = 0; 4385 }
4386// else
4387// {
4388// bitch(Rd, "Unable to read user record.", "Nothing available to look up a user record with.");
4389// rt = 1;
4390// }
4391
4392 if (0 == rt)
4393 {
4394 ret += 1;
4395 Rd->database->putstr(Rd->database, "UserAccounts.FirstName", first);
4396 Rd->database->putstr(Rd->database, "UserAccounts.LastName", last);
4397 Rd->database->putstr(Rd->database, "UserAccounts.Email", getStrH(tnm, "email"));
4398 Rd->database->putstr(Rd->database, "UserAccounts.Created", getStrH(tnm, "created"));
4399 Rd->database->putstr(Rd->database, "UserAccounts.PrincipalID", getStrH(tnm, "UUID"));
4400 Rd->database->putstr(Rd->database, "UserAccounts.UserLevel", getStrH(tnm, "level"));
4401 Rd->database->putstr(Rd->database, "UserAccounts.UserFlags", getStrH(tnm, "flags"));
4402 Rd->database->putstr(Rd->database, "UserAccounts.UserTitle", getStrH(tnm, "title"));
4403 Rd->database->putstr(Rd->database, "UserAccounts.active", getStrH(tnm, "active"));
4404 Rd->database->putstr(Rd->database, "auth.passwordSalt", getStrH(tnm, "passwordSalt"));
4405 Rd->database->putstr(Rd->database, "auth.passwordHash", getStrH(tnm, "passwordHash"));
4406 Rd->stuff-> putstr(Rd->stuff, "linky-hashish", getStrH(tnm, "linky-hashish"));
4407 Rd->database->putstr(Rd->database, "Lua.name", getStrH(tnm, "name"));
4408 Rd->database->putstr(Rd->database, "Lua.DoB", getStrH(tnm, "DoB"));
4409 Rd->database->putstr(Rd->database, "Lua.agree", getStrH(tnm, "agree"));
4410 Rd->database->putstr(Rd->database, "Lua.adult", getStrH(tnm, "adult"));
4411 Rd->database->putstr(Rd->database, "Lua.aboutMe", getStrH(tnm, "aboutMe"));
4412 Rd->database->putstr(Rd->database, "Lua.vouched", getStrH(tnm, "vouched"));
4413 }
4414 else if (rows)
4415 {
4416 ret += rows->rows->size(rows->rows);
4417 if (1 == ret)
4418 {
4419 dbPull(Rd, "UserAccounts", rows);
4420 dbDoSomething(auth, FALSE, getStrH(Rd->database, "UserAccounts.PrincipalID"));
4421 rows = auth->rows;
4027 if (rows) 4422 if (rows)
4028 i = rows->rows->size(rows->rows);
4029 else
4030 { 4423 {
4031 bitch(Rd, "Internal error.", "Matching UUID record found in UserAccounts."); 4424 if (1 == rows->rows->size(rows->rows))
4032 ret++; 4425 dbPull(Rd, "auth", rows);
4033 break;
4034 } 4426 }
4035 } while (i != 0);
4036 if (0 == ret)
4037 {
4038 data->putstr(data, "UUID", xstrdup(uuid));
4039 Rd->stuff->putstr(Rd->stuff, "UUID", xstrdup(uuid));
4040 } 4427 }
4041 rows = NULL;
4042 } 4428 }
4043 else if ((strcmp("confirm", Rd->doit) == 0) || (strcmp("logout", Rd->doit) == 0)) 4429 else
4430 {
4431 d("No user name or UUID to get an account for.");
4432 }
4433
4434 if (1 == ret)
4044 { 4435 {
4045 t = getStrH(data, "UUID"); 4436// TODO - this has to change when we are editing other peoples accounts.
4046 if (36 != strlen(t)) 4437 Rd->shs.UUID = Rd->database->getstr(Rd->database, "UserAccounts.PrincipalID", true);
4438 Rd->stuff->putstr(Rd->stuff, "email", getStrH(Rd->database, "UserAccounts.Email"));
4439 Rd->shs.level = atoi(getStrH(Rd->database, "UserAccounts.UserLevel"));
4440 }
4441
4442 tnm->free(tnm);
4443 return ret;
4444}
4445
4446static int accountDel(reqData *Rd, inputForm *iF, inputValue *iV)
4447{
4448 int ret = 0;
4449 int c = accountRead(Rd, iF, iV);
4450
4451 if (1 != c)
4452 {
4453 bitch(Rd, "Cannot delete account.", "Account doesn't exist.");
4454 ret++;
4455 }
4456 else
4457 {
4458// check if logged in user is allowed to delete this account
4459// delete user record
4460// log the user out if they are logged in
4461 }
4462 return ret;
4463}
4464static int accountCreate(reqData *Rd, inputForm *iF, inputValue *iV)
4465{
4466 int ret = 0;
4467 int c = accountRead(Rd, iF, iV);
4468 boolean wipe = FALSE;
4469
4470 if (strcmp("POST", Rd->Method) == 0)
4471 {
4472 if (0 != c)
4047 { 4473 {
4048 bitch(Rd, "Internal error.", "UUID isn't long enough."); 4474 bitch(Rd, "Cannot create account.", "Account exists.");
4049 ret++; 4475 ret++;
4050 } 4476 }
4051 else 4477 else
4052 strcpy(uuid, t); 4478 {
4479 char *salt = newSLOSsalt(Rd);
4480 char *h = checkSLOSpassword(Rd, salt, getStrH(Rd->body, "password"), NULL, NULL);
4481
4482 if (NULL == h)
4483 ret++;
4484 else
4485 {
4486 Rd->stuff->putstr(Rd->stuff, "passHash", h);
4487 Rd->stuff->putstr(Rd->stuff, "passSalt", salt);
4488 free(h);
4489 }
4490 free(salt);
4491 if (0 != ret)
4492 {
4493 wipe = TRUE;
4494 Rd->shs.UUID = NULL;
4495 Rd->output = "accountLogin";
4496 }
4497 }
4053 } 4498 }
4054 else 4499 freeSesh(Rd, FALSE, wipe);
4500 newSesh(Rd, FALSE);
4501 return ret;
4502}
4503static int accountAdd(reqData *Rd, inputForm *iF, inputValue *iV)
4504{
4505 int ret = 0;
4506 int c = accountRead(Rd, iF, iV);
4507 boolean wipe = FALSE;
4508
4509 if (0 != c)
4510 {
4511 bitch(Rd, "Cannot add account.", "Account exists.");
4512 ret++;
4513 }
4514 else if ((0 == ret) && (strcmp("POST", Rd->Method) == 0))
4055 { 4515 {
4056 if ('\0' != getStrH(Rd->database, "UserAccounts.ScopeID")[0]) return ret; 4516 char *h = checkSLOSpassword(Rd, getStrH(Rd->stuff, "passSalt"), getStrH(Rd->stuff, "password"), getStrH(Rd->stuff, "passHash"), "Passwords are not the same.");
4057 4517
4058 t = getStrH(data, "UUID"); 4518 if (NULL == h)
4059 if (36 != strlen(t))
4060 { 4519 {
4061 bitch(Rd, "Internal error.", "UUID isn't long enough.");
4062 ret++; 4520 ret++;
4521 wipe = TRUE;
4522 Rd->shs.UUID = NULL;
4523 Rd->output = "accountLogin";
4063 } 4524 }
4064 else 4525 else
4065 { 4526 {
4066 strcpy(uuid, t); 4527 free(h);
4067 dbDoSomething(uuids, FALSE, uuid); 4528 generateAccountUUID(Rd);
4068 rows = uuids->rows; 4529 Rd->stuff->putstr(Rd->stuff, "passwordHash", getStrH(Rd->stuff, "passHash"));
4069 if (rows) 4530 Rd->stuff->putstr(Rd->stuff, "passwordSalt", getStrH(Rd->stuff, "passSalt"));
4070 { 4531 Rd->shs.level = -200;
4071 if (1 != rows->rows->size(rows->rows)) 4532 freeSesh(Rd, FALSE, wipe);
4072 { 4533 newSesh(Rd, TRUE);
4073 bitch(Rd, "Internal error.", "No matching UUID record found in UserAccounts."); 4534 accountWrite(Rd);
4074 ret++; 4535// log them in
4075 } 4536 I("Logged on %s %s Level %d %s", Rd->shs.UUID, getStrH(Rd->stuff, "name"), Rd->shs.level, getLevel(Rd));
4076 } 4537 Rd->output = "accountView";
4538 Rd->form = "accountView";
4539 Rd->doit = "login";
4540// TODO - send email. Quick and easy is to invoke the sandmail command. Later use libcurl.
4077 } 4541 }
4078 } 4542 }
4543 freeSesh(Rd, FALSE, wipe);
4544 newSesh(Rd, FALSE);
4545 return ret;
4546}
4547
4548static int accountSave(reqData *Rd, inputForm *iF, inputValue *iV)
4549{
4550 int ret = 0;
4551 int c = accountRead(Rd, iF, iV);
4552 boolean wipe = FALSE;
4079 4553
4080 if (!badBoy(ret, Rd, data, "UUID", uuid)) 4554 if (1 != c)
4555 {
4556 bitch(Rd, "Cannot save account.", "Account doesn't exist.");
4557 ret++;
4558 }
4559 else if ((0 == ret) && (strcmp("POST", Rd->Method) == 0))
4081 { 4560 {
4082 if (rows) 4561 char *h = checkSLOSpassword(Rd, getStrH(Rd->stuff, "passSalt"), getStrH(Rd->body, "password"), getStrH(Rd->stuff, "passHash"), "Passwords are not the same.");
4562 if (NULL == h)
4083 { 4563 {
4084 dbPull(Rd, "UserAccounts", rows); 4564 ret++;
4085 Rd->stuff->putstr(Rd->stuff, "level", getStrH(Rd->database, "UserAccounts.Userlevel")); 4565 wipe = TRUE;
4086 free(rows->rows); 4566 Rd->shs.UUID = NULL;
4087 free(rows->fieldNames); 4567 Rd->output = "accountLogin";
4088 free(rows);
4089 } 4568 }
4090 else 4569 else
4091 { 4570 {
4092 Rd->database->putstr(Rd->database, "UserAccounts.PrincipalID", xstrdup(uuid)); 4571 free(h);
4572 Rd->stuff->putstr(Rd->stuff, "passwordHash", getStrH(Rd->stuff, "passHash"));
4573 Rd->stuff->putstr(Rd->stuff, "passwordSalt", getStrH(Rd->stuff, "passSalt"));
4574 accountWrite(Rd);
4093 } 4575 }
4094 Rd->stuff->putstr(Rd->stuff, "UUID", uuid);
4095 } 4576 }
4577 freeSesh(Rd, FALSE, wipe);
4578 newSesh(Rd, FALSE);
4579 return ret;
4580}
4096 4581
4582static int accountValidate(reqData *Rd, inputForm *iF, inputValue *iV)
4583{
4584 int ret = 0;
4585 int c = accountRead(Rd, iF, iV);
4586 boolean wipe = FALSE;
4587
4588 if (1 != c)
4589 {
4590 bitch(Rd, "Cannot validate account.", "Account doesn't exist.");
4591 ret++;
4592 }
4593 else
4594 {
4595 Rd->stuff->putstr(Rd->stuff, "email", getStrH(Rd->database, "UserAccounts.Email"));
4596 Rd->stuff->putstr(Rd->stuff, "created", getStrH(Rd->database, "UserAccounts.Created"));
4597 Rd->stuff->putstr(Rd->stuff, "flags", getStrH(Rd->database, "UserAccounts.UserFlags"));
4598 Rd->stuff->putstr(Rd->stuff, "active", getStrH(Rd->database, "UserAccounts.active"));
4599 Rd->stuff->putstr(Rd->stuff, "passwordSalt", getStrH(Rd->database, "auth.passwordSalt"));
4600 Rd->stuff->putstr(Rd->stuff, "passwordHash", getStrH(Rd->database, "auth.passwordHash"));
4601 Rd->stuff->putstr(Rd->stuff, "name", getStrH(Rd->database, "Lua.name"));
4602 Rd->stuff->putstr(Rd->stuff, "DoB", getStrH(Rd->database, "Lua.DoB"));
4603 Rd->stuff->putstr(Rd->stuff, "agree", getStrH(Rd->database, "Lua.agree"));
4604 Rd->stuff->putstr(Rd->stuff, "adult", getStrH(Rd->database, "Lua.adult"));
4605 Rd->stuff->putstr(Rd->stuff, "aboutMe", getStrH(Rd->database, "Lua.aboutMe"));
4606 Rd->stuff->putstr(Rd->stuff, "vouched", getStrH(Rd->database, "Lua.vouched"));
4607 Rd->shs.level = -100;
4608 accountWrite(Rd);
4609 wipe = TRUE;
4610 }
4611 freeSesh(Rd, FALSE, wipe);
4612 newSesh(Rd, FALSE);
4097 return ret; 4613 return ret;
4098} 4614}
4099 4615
4100 4616
4101void loginPage(reqData *Rd, char *message) 4617static int accountView(reqData *Rd, inputForm *iF, inputValue *iV)
4102{ 4618{
4103 char *name = xstrdup(getStrH(Rd->stuff, "name")), *linky = checkLinky(Rd); 4619// TODO - this has to change when we are editing other peoples accounts.
4620 int ret = 0;
4621 int c = accountRead(Rd, iF, iV);
4622 boolean wipe = FALSE;
4104 4623
4105 Rd->stuff->remove(Rd->stuff, "UUID"); 4624d("Sub accountView %s %s %s", getStrH(Rd->database, "UserAccounts.PrincipalID"), getStrH(Rd->database, "UserAccounts.FirstName"), getStrH(Rd->database, "UserAccounts.LastName"));
4106 HTMLheader(Rd->reply, "<!--#echo var=\"grid\" --> account manager"); 4625 if (1 != c)
4107 if (DEBUG) HTMLdebug(Rd->reply); 4626 {
4108 Rd->reply->addstrf(Rd->reply, "<h1><!--#echo var=\"grid\" --> account manager</h1>\n"); 4627 bitch(Rd, "Cannot view account.", "Account doesn't exist.");
4109 Rd->reply->addstr(Rd->reply, linky); 4628 ret++;
4110 free(linky); 4629 wipe = TRUE;
4111 if (0 != Rd->errors->size(Rd->messages)) 4630 Rd->shs.UUID = NULL;
4112 HTMLlist(Rd->reply, "messages -", Rd->messages); 4631 Rd->output = "accountLogin";
4113 HTMLform(Rd->reply, "", Rd->shs.munchie); 4632 }
4114 HTMLtext(Rd->reply, "text", "name", "name", name, 42, 63, TRUE); 4633 else
4115 HTMLtext(Rd->reply, "password", "password", "password", "", 16, 0, TRUE); 4634 {
4116 Rd->reply->addstr(Rd->reply, "<p>Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters.</p>\n"); 4635 // Check password on POST if the session user is the same as the shown user, coz this is the page shown on login.
4117 Rd->reply->addstr(Rd->reply, "<p>While viewers will usually remember your name and password for you, you'll need to remember it for this web site to. &nbsp; " 4636 if ((strcmp("POST", Rd->Method) == 0) && (strcmp(Rd->shs.UUID, getStrH(Rd->database, "UserAccounts.PrincipalID")) == 0))
4118 "I highly recommend using a password manager. &nbsp; KeePass and it's variations is a great password manager.</p>\n"); 4637 {
4119 Rd->reply->addstrf(Rd->reply, "<input type='submit' disabled style='display: none' aria-hidden='true' />\n"); // Stop Enter key on text fields triggering the first submit button. 4638 char *h = checkSLOSpassword(Rd, getStrH(Rd->database, "auth.passwordSalt"), getStrH(Rd->body, "password"), getStrH(Rd->database, "auth.passwordHash"), "Login failed.");
4120 HTMLbutton(Rd->reply, "login"); 4639 if (NULL == h)
4121 HTMLbutton(Rd->reply, "create"); 4640 {
4122 if (0 != Rd->errors->size(Rd->errors)) 4641 ret++;
4123 HTMLlist(Rd->reply, "errors -", Rd->errors); 4642 wipe = TRUE;
4124 Rd->reply->addstrf(Rd->reply, "<p>%s</p>\n", message); 4643 Rd->shs.UUID = NULL;
4125 HTMLfooter(Rd->reply); 4644 Rd->output = "accountLogin";
4126 free(name); 4645 }
4646 else
4647 {
4648 free(h);
4649 I("Logged on %s %s Level %d %s", Rd->shs.UUID, getStrH(Rd->stuff, "name"), Rd->shs.level, getLevel(Rd));
4650 }
4651 }
4652 }
4653 freeSesh(Rd, FALSE, wipe);
4654 newSesh(Rd, FALSE);
4655
4656 return ret;
4127} 4657}
4658static int accountEdit(reqData *Rd, inputForm *iF, inputValue *iV)
4659{
4660 int ret = 0;
4661 int c = accountRead(Rd, iF, iV);
4128 4662
4129void accountCreationPage(reqData *Rd, char *message) 4663d("Sub accountView %s %s %s", getStrH(Rd->database, "UserAccounts.PrincipalID"), getStrH(Rd->database, "UserAccounts.FirstName"), getStrH(Rd->database, "UserAccounts.LastName"));
4664 if (1 != c)
4665 {
4666 bitch(Rd, "Cannot edit account.", "Account doesn't exist.");
4667 ret++;
4668 }
4669 else
4670 {
4671// check if logged in user is allowed to make these changes
4672// update user record
4673 }
4674 return ret;
4675}
4676static int accountExplore(reqData *Rd, inputForm *iF, inputValue *iV)
4130{ 4677{
4131 char *name = getStrH(Rd->body, "name"), *linky = checkLinky(Rd); 4678 int ret = 0;
4132 char *toke_n_munchie = getCookie(Rd->Rcookies, "toke_n_munchie"); 4679// get a list of user records
4133 char *tmp = xmalloc(16), *t; 4680 return ret;
4134 int i, d; 4681}
4682static int accountOut(reqData *Rd, inputForm *iF, inputValue *iV)
4683{
4684 int ret = 0;
4685 int c = accountRead(Rd, iF, iV);
4135 4686
4136 if ('\0' == name[0]) 4687 if (1 != c)
4137 name = getStrH(Rd->stuff, "name"); 4688 {
4689// bitch(Rd, "Cannot logout account.", "Account doesn't exist.");
4690// ret++;
4691 }
4692 else
4693 {
4694// log the user out if they are logged in
4695 Rd->shs.UUID = NULL;
4696 Rd->output = "accountLogin";
4697 }
4138 4698
4139// TODO - eww lots of memory leaks here. 4699 freeSesh(Rd, FALSE, TRUE);
4140// TODO - need to check if qLibc does it's own free() calls, and fill in the gaps for when it doesn't. 4700 newSesh(Rd, FALSE);
4141 HTMLheader(Rd->reply, "<!--#echo var=\"grid\" --> account manager"); 4701 return ret;
4142 if (DEBUG) HTMLdebug(Rd->reply); 4702}
4143 Rd->reply->addstrf(Rd->reply, "<h1><!--#echo var=\"grid\" --> account manager</h1>\n");
4144 Rd->reply->addstrf(Rd->reply, "<h2>Creating <!--#echo var=\"grid\" --> account for %s</h2>\n", name);
4145 Rd->reply->addstr(Rd->reply, linky);
4146 free(linky);
4147 if (0 != Rd->errors->size(Rd->messages))
4148 HTMLlist(Rd->reply, "messages -", Rd->messages);
4149// TODO - set this to autocomplete="off".
4150// TODO - autofill most fields on error and redisplay.
4151 HTMLform(Rd->reply, "", Rd->shs.munchie);
4152 HTMLhidden(Rd->reply, "name", name);
4153 HTMLhidden(Rd->reply, "UUID", getStrH(Rd->stuff, "UUID"));
4154 HTMLtext(Rd->reply, "password", "Re-enter your password", "password", "", 16, 0, FALSE);
4155 Rd->reply->addstr(Rd->reply, "<p>Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters.</p>\n");
4156 Rd->reply->addstr(Rd->reply, "<p>While viewers will usually remember your name and password for you, you'll need to remember it for this web site to. &nbsp; "
4157 "I highly recommend using a password manager. &nbsp; KeePass and it's variations is a great password manager.</p>\n");
4158 HTMLtext(Rd->reply, "email", "email", "email", getStrH(Rd->body, "email"), 42, 254, FALSE);
4159 HTMLtext(Rd->reply, "email", "Repeat your email, to be sure you got it correct", "emayl", getStrH(Rd->body, "emayl"), 42, 254, FALSE);
4160 Rd->reply->addstr(Rd->reply, "<p>A validation email will be sent to this email address, you will need to click on the link in it to continue your account creation.</p>\n");
4161 Rd->reply->addstr(Rd->reply, "<table><tr><td>\n");
4162 HTMLselect(Rd->reply, "Date of birth", "year");
4163 t = getStrH(Rd->body, "year");
4164 if (NULL == t)
4165 d = -1;
4166 else
4167 d = atoi(t);
4168 HTMLoption(Rd->reply, xstrdup(""), FALSE);
4169 for (i = 1900; i <= 2020; i++)
4170 {
4171 boolean sel = FALSE;
4172 4703
4173 if (i == d)
4174 sel = TRUE;
4175 sprintf(tmp, "%d", i);
4176 HTMLoption(Rd->reply, xstrdup(tmp), sel);
4177 }
4178 HTMLselectEnd(Rd->reply);
4179 Rd->reply->addstr(Rd->reply, "</tt><td>");
4180 HTMLselect(Rd->reply, NULL, "month");
4181 t = getStrH(Rd->body, "month");
4182 HTMLoption(Rd->reply, xstrdup(""), FALSE);
4183 for (i = 0; i <= 11; i++)
4184 {
4185 boolean sel = FALSE;
4186 4704
4187 if ((NULL != t) && (strcmp(t, months[i]) == 0)) 4705qhashtbl_t *accountPages = NULL;
4188 sel = TRUE; 4706inputForm *newInputForm(char *name, char *title, char *help, inputFormShowFunc web, inputFormShowFunc eWeb)
4189 HTMLoption(Rd->reply, months[i], sel); 4707{
4190 } 4708 inputForm *ret = xmalloc(sizeof(inputForm));
4191 HTMLselectEnd(Rd->reply); 4709
4192 Rd->reply->addstr(Rd->reply, "</td></tr></table>\n"); 4710d("newInputForm(%s)", name);
4193 HTMLcheckBox(Rd->reply, "adult", "I'm allegedly an adult in my country.", !strcmp("on", getStrH(Rd->body, "adult")), TRUE); 4711 ret->name = name; ret->title = title; ret->help = help;
4194 HTMLcheckBox(Rd->reply, "agree", "I accept the Terms of Service.", !strcmp("on", getStrH(Rd->body, "agree")), TRUE); 4712 ret->web = web; ret->eWeb = eWeb;
4195 Rd->reply->addstrf(Rd->reply, "<h2>Terms of Service</h2><pre>%s</pre>\n", getStrH(Rd->configs, "ToS")); 4713 ret->fields = qlisttbl(QLISTTBL_THREADSAFE | QLISTTBL_UNIQUE | QLISTTBL_LOOKUPFORWARD);
4196 // For maxlength - the MySQL database field is type text, which has a max length of 64 Kilobytes byets, but characters might take up 1 - 4 bytes, and maxlength is in characters. 4714 ret->subs = qhashtbl(0, 0);
4197 // For rows and cols, seems a bit broken, I ask for 5/42, I get 6,36. In world it seems to be 7,46 4715 accountPages->put(accountPages, ret->name, ret, sizeof(inputForm));
4198// TODO - check against the limit for in world profiles, coz this will become that. 4716 free(ret);
4199// TODO - validate aboutMe, it should not be empty, and should not be longer than 64 kilobytes. 4717 return accountPages->get(accountPages, name, NULL, false);
4200 HTMLtextArea(Rd->reply, "aboutMe", "About me", 7, 50, 4, 16384, "Describe yourself here.", "off", "true", "soft", getStrH(Rd->body, "aboutMe"), FALSE);
4201// TODO - upload an icon / profile picture.
4202 Rd->reply->addstrf(Rd->reply, "<input type='submit' disabled style='display: none' aria-hidden='true' />\n"); // Stop Enter key on text fields triggering the first submit button.
4203 HTMLbutton(Rd->reply, "confirm");
4204 HTMLbutton(Rd->reply, "cancel");
4205 if (0 != Rd->errors->size(Rd->errors))
4206 HTMLlist(Rd->reply, "errors -", Rd->errors);
4207 Rd->reply->addstrf(Rd->reply, "<p>%s</p>\n", message);
4208 HTMLfooter(Rd->reply);
4209} 4718}
4210 4719
4211void loggedOnPage(reqData *Rd, char *message) 4720inputField *addInputField(inputForm *iF, signed char type, char *name, char *title, char *help, inputFieldValidFunc validate, inputFieldShowFunc web)
4212{ 4721{
4213 char *name = getStrH(Rd->stuff, "name"), *linky = checkLinky(Rd); 4722 inputField *ret = xzalloc(sizeof(inputField));
4214 char *toke_n_munchie = getCookie(Rd->Rcookies, "toke_n_munchie");
4215 4723
4216 HTMLheader(Rd->reply, "<!--#echo var=\"grid\" --> account manager"); 4724//d("addInputField(%s, %s)", iF->name, name);
4217 if (DEBUG) HTMLdebug(Rd->reply); 4725 ret->name = name; ret->title = title; ret->help = help;
4218 Rd->reply->addstrf(Rd->reply, "<h1><!--#echo var=\"grid\" --> account manager</h1>\n"); 4726 ret->validate = validate; ret->web = web; ret->type = type;
4219 Rd->reply->addstrf(Rd->reply, "<h2><!--#echo var=\"grid\" --> account for %s</h2>\n", name); 4727 ret->flags = FLD_EDITABLE;
4220 Rd->reply->addstr(Rd->reply, linky); 4728 iF->fields->put(iF->fields, ret->name, ret, sizeof(inputField));
4221 free(linky); 4729 free(ret);
4222 if (0 != Rd->errors->size(Rd->messages)) 4730 return iF->fields->get(iF->fields, name, NULL, false);
4223 HTMLlist(Rd->reply, "messages -", Rd->messages); 4731}
4224 HTMLform(Rd->reply, "", Rd->shs.munchie); 4732
4225 HTMLhidden(Rd->reply, "name", name); 4733void inputFieldExtra(inputField *ret, signed char flags, short viewLength, short maxLength)
4226 HTMLhidden(Rd->reply, "UUID", getStrH(Rd->stuff, "UUID")); 4734{
4227 HTMLtext(Rd->reply, "email", "email", "email", getStrH(Rd->database, "UserAccounts.Email"), 42, 254, FALSE); 4735 ret->flags = flags;
4228 HTMLtext(Rd->reply, "Old password", "password", "password", "", 16, 0, FALSE); 4736 ret->viewLength = viewLength; ret->maxLength = maxLength;
4229 Rd->reply->addstr(Rd->reply, "<p>Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters.</p>\n");
4230// HTMLtext(Rd->reply, "title", "text", "title", getStrH(Rh->stuff, "title"), 16, 64, TRUE);
4231 HTMLselect(Rd->reply, "type", "type");
4232 HTMLoption(Rd->reply, "", false);
4233 HTMLoption(Rd->reply, "newbie", true);
4234 HTMLoption(Rd->reply, "validated", true);
4235 HTMLoption(Rd->reply, "vouched for", true);
4236 HTMLoption(Rd->reply, "approved", true);
4237 HTMLoption(Rd->reply, "disabled", false);
4238 HTMLoption(Rd->reply, "god", false);
4239 HTMLselectEnd(Rd->reply);
4240 Rd->reply->addstrf(Rd->reply, "<input type='submit' disabled style='display: none' aria-hidden='true' />\n"); // Stop Enter key on text fields triggering the first submit button.
4241 HTMLbutton(Rd->reply, "delete");
4242 HTMLbutton(Rd->reply, "list");
4243 HTMLbutton(Rd->reply, "logout");
4244 HTMLbutton(Rd->reply, "update");
4245 if (0 != Rd->errors->size(Rd->errors))
4246 HTMLlist(Rd->reply, "errors -", Rd->errors);
4247 HTMLfooter(Rd->reply);
4248} 4737}
4249 4738
4739void addSession(inputForm *iF)
4740{
4741 inputField *fld, **flds = xzalloc(3 * sizeof(*flds));
4742
4743//d("addSession(%s)", iF->name);
4744 flds[0] = addInputField(iF, LUA_TSTRING, "hashish", "hashish", "", sessionValidate, sessionWeb);
4745 inputFieldExtra(flds[0], FLD_HIDDEN, 0, 0);
4746 flds[1] = addInputField(iF, LUA_TSTRING, "toke_n_munchie", "toke_n_munchie", "", sessionValidate, sessionWeb);
4747 inputFieldExtra(flds[1], FLD_HIDDEN, 0, 0);
4748 fld = addInputField(iF, LUA_TGROUP, "sessionGroup", "sessionGroup", "", sessionValidate, sessionWeb);
4749 inputFieldExtra(fld, FLD_HIDDEN, 0, 0);
4750 fld->group = flds;
4751 flds[0]->group = flds;
4752 flds[1]->group = flds;
4753}
4754
4755void addEmailFields(inputForm *iF)
4756{
4757 inputField *fld, **flds = xzalloc(3 * sizeof(*flds));
4758
4759 flds[0] = addInputField(iF, LUA_TEMAIL, "email", "email", NULL, emailValidate, emailWeb);
4760 inputFieldExtra(flds[0], FLD_EDITABLE, 42, 254);
4761 flds[1] = addInputField(iF, LUA_TEMAIL, "emayl", "Re-enter your email, to be sure you got it correct",
4762 "A validation email will be sent to this email address, you will need to click on the link in it to continue your account creation.", emailValidate, emailWeb);
4763 inputFieldExtra(flds[1], FLD_EDITABLE, 42, 254);
4764 fld = addInputField(iF, LUA_TGROUP, "emailGroup", "emailGroup", "", emailValidate, NULL);
4765 inputFieldExtra(fld, FLD_HIDDEN, 0, 0);
4766 fld->group = flds;
4767 flds[0]->group = flds;
4768 flds[1]->group = flds;
4769}
4770
4771void addDoBFields(inputForm *iF)
4772{
4773 inputField *fld, **flds = xzalloc(3 * sizeof(*flds));
4774
4775 flds[0] = addInputField(iF, LUA_TSTRING, "DoByear", "year", NULL, DoBValidate, DoByWeb);
4776 flds[1] = addInputField(iF, LUA_TSTRING, "DoBmonth", "month", NULL, DoBValidate, DoBmWeb);
4777 fld = addInputField(iF, LUA_TGROUP, "DoBGroup", "DoBGroup", "", DoBValidate, DoBWeb);
4778 inputFieldExtra(fld, FLD_HIDDEN, 0, 0);
4779 fld->group = flds;
4780 flds[0]->group = flds;
4781 flds[1]->group = flds;
4782}
4783
4784void addLegalFields(inputForm *iF)
4785{
4786 inputField *fld, **flds = xzalloc(3 * sizeof(*flds));
4787
4788 flds[0] = addInputField(iF, LUA_TBOOLEAN, "adult", "I'm allegedly an adult in my country.", NULL, legalValidate, adultWeb);
4789 flds[1] = addInputField(iF, LUA_TBOOLEAN, "agree", "I accept the Terms of Service.", NULL, legalValidate, agreeWeb);
4790 fld = addInputField(iF, LUA_TGROUP, "legalGroup", "legalGroup", "", legalValidate, legalWeb);
4791 inputFieldExtra(fld, FLD_HIDDEN, 0, 0);
4792 fld->group = flds;
4793 flds[0]->group = flds;
4794 flds[1]->group = flds;
4795}
4796
4797inputSub *addSubmit(inputForm *iF, char *name, char *title, char *help, inputSubmitFunc submit, char *output)
4798{
4799 inputSub *ret = xmalloc(sizeof(inputSub));
4800
4801//d("addSubmit(%s, %s)", iF->name, name);
4802 ret->name = name; ret->title = title; ret->help = help; ret->submit = submit; ret->outputForm = output;
4803 iF->subs->put(iF->subs, ret->name, ret, sizeof(inputSub));
4804 free(ret);
4805 return iF->subs->get(iF->subs, name, NULL, false);
4806}
4807
4808
4809/* There should be some precedence for values overriding values here.
4810 https://www.w3.org/standards/webarch/protocols
4811 "This intro text is boilerplate for the beta release of w3.org." Fucking useless. Pffft
4812 https://www.w3.org/Protocols/
4813 Still nothing official, though the ENV / HEADER stuff tends to be about the protocol things, and cookies / body / queries are about the data things.
4814 http://docs.gantry.org/gantry4/advanced/setby
4815 Says that query overrides cookies, but that might be just for their platform.
4816 https://framework.zend.com/manual/1.11/en/zend.controller.request.html
4817 Says - "1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV."
4818 We don't actually get the headers directly, it's all sent via the env.
4819
4820URL query Values actually provided by the user in the FORM, and other things.
4821POST body Values actually provided by the user in the FORM.
4822cookies
4823 https://stackoverflow.com/questions/4056306/how-to-handle-multiple-cookies-with-the-same-name
4824
4825headers includes HTTP_COOKIE and QUERY_STRING
4826env includes headers and HTTP_COOKIE and QUERY_STRING
4827
4828database Since all of the above are for updating the database anyway, this goes on the bottom, overridden by all.
4829 Though be wary of security stuff.
4830
4831 Sending cookie headers is a special case, multiples can be sent, otherwise headers are singletons, only send one for each name.
4832*/
4833char *sourceTypes[] =
4834{
4835 "cookies",
4836 "body",
4837 "queries",
4838 "stuff"
4839};
4840
4841static int collectFields(reqData *Rd, inputForm *iF, inputValue *iV, int t)
4842{
4843 int i = 0, j;
4844 qlisttbl_obj_t obj;
4845
4846 memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
4847 iF->fields->lock(iF->fields);
4848 while(iF->fields->getnext(iF->fields, &obj, NULL, false) == true)
4849 {
4850 inputField *fld = (inputField *) obj.data;
4851
4852//if (0 > t)
4853// d("Collecting %d %s - %s", t, iF->name, fld->name);
4854//else
4855// d("Collecting %s %s - %s", sourceTypes[t], iF->name, fld->name);
4856 iV[i].field = fld;
4857 if (LUA_TGROUP == fld->type)
4858 {
4859 if (0 >= t)
4860 {
4861 j = 0;
4862 // If it's a group, number the members relative to the group field.
4863 // Assume the members for this group are the previous ones.
4864 while (iV[i].field->group[j])
4865 {
4866 j++;
4867 iV[i - j].index = j;
4868 }
4869 }
4870 }
4871 else
4872 {
4873 char *vl = NULL;
4874
4875 switch (t)
4876 {
4877 // We don't get the cookies metadata.
4878 case 0 : vl = Rd->cookies->getstr(Rd->cookies, obj.name, false); break;
4879 case 1 : vl = Rd->body-> getstr(Rd->body, obj.name, false); break;
4880 case 2 : vl = Rd->queries->getstr(Rd->queries, obj.name, false); break;
4881 case 3 : vl = Rd->queries->getstr(Rd->stuff, obj.name, false); break;
4882 // Rd->stuff comes from the database reads and calculated stuff, which we do later with this new architecture. The old version validated Rd->stuff as well.
4883 // It was doing that so we can pick up any UUID, validate it, and read in relevant records.
4884 // For newbies their UUID was generated during the validation of name.
4885 // Should still validate that it's long enough and in the correct format, coz that's coming from RD->body sometimes.
4886 // The rest can happen in the submit functions now.
4887 default: break;
4888 }
4889 if ((NULL != iV[i].value) && (NULL != vl))
4890 {
4891 if (strcmp(vl, iV[i].value) != 0)
4892 W("Collected %s value for %s - %s from %s overriding value from %s", sourceTypes[t], iF->name, fld->name, sourceTypes[t], sourceTypes[iV[i].source]);
4893 else
4894 W("Collected %s value for %s - %s from %s same as value from %s", sourceTypes[t], iF->name, fld->name, sourceTypes[t], sourceTypes[iV[i].source]);
4895 }
4896 if (NULL != vl)
4897 {
4898 iV[i].source = t;
4899 iV[i].value = vl;
4900 D("Collected %s value for %s - %s = %s", sourceTypes[t], iF->name, fld->name, vl);
4901 }
4902 }
4903 i++;
4904 }
4905 iF->fields->unlock(iF->fields);
4906 return i;
4907}
4908
4909
4250void listPage(reqData *Rd, char *message) 4910void listPage(reqData *Rd, char *message)
4251{ 4911{
4252// TODO - should check if the user is a god before allowing this. 4912// TODO - should check if the user is a god before allowing this.
@@ -4267,8 +4927,9 @@ void listPage(reqData *Rd, char *message)
4267 NULL, NULL, "Name"), 4927 NULL, NULL, "Name"),
4268 "member accounts", NULL, NULL); 4928 "member accounts", NULL, NULL);
4269 HTMLform(Rd->reply, "", Rd->shs.munchie); 4929 HTMLform(Rd->reply, "", Rd->shs.munchie);
4930 HTMLhidden(Rd->reply, "form", "accountExplore");
4270 HTMLhidden(Rd->reply, "name", name); 4931 HTMLhidden(Rd->reply, "name", name);
4271 HTMLhidden(Rd->reply, "UUID", getStrH(Rd->stuff, "UUID")); 4932 HTMLhidden(Rd->reply, "UUID", Rd->shs.UUID);
4272 Rd->reply->addstrf(Rd->reply, "<input type='submit' disabled style='display: none' aria-hidden='true' />\n"); // Stop Enter key on text fields triggering the first submit button. 4933 Rd->reply->addstrf(Rd->reply, "<input type='submit' disabled style='display: none' aria-hidden='true' />\n"); // Stop Enter key on text fields triggering the first submit button.
4273 HTMLbutton(Rd->reply, "me"); 4934 HTMLbutton(Rd->reply, "me");
4274 HTMLbutton(Rd->reply, "logout"); 4935 HTMLbutton(Rd->reply, "logout");
@@ -4278,91 +4939,25 @@ void listPage(reqData *Rd, char *message)
4278 HTMLfooter(Rd->reply); 4939 HTMLfooter(Rd->reply);
4279} 4940}
4280 4941
4942
4281void account_html(char *file, reqData *Rd, HTMLfile *thisFile) 4943void account_html(char *file, reqData *Rd, HTMLfile *thisFile)
4282{ 4944{
4945 inputForm *iF;
4946 inputField *fld;
4947 inputSub *sub;
4283 boolean isGET = FALSE; 4948 boolean isGET = FALSE;
4284 int e = 0; 4949 int e = 0, t = 0, i, j;
4285 char *doit = getStrH(Rd->body, "doit"); 4950 char *doit = getStrH(Rd->body, "doit"), *form = getStrH(Rd->body, "form");
4286
4287 C("Starting dynamic page %s", file);
4288 Rd->func = NULL;
4289
4290 if (NULL == fieldValidFuncs)
4291 {
4292 fieldValidFuncs = qlisttbl(QLISTTBL_LOOKUPFORWARD | QLISTTBL_THREADSAFE | QLISTTBL_UNIQUE);
4293 newValidFunc("hashish", "session", (fieldValidFunc) validateSesh);
4294 newValidFunc("toke_n_munchie", "session", (fieldValidFunc) validateSesh);
4295 newValidFunc("UUID", "UUID", (fieldValidFunc) validateUUID);
4296 newValidFunc("name", "name", (fieldValidFunc) validateName);
4297 newValidFunc("password", "password", (fieldValidFunc) validatePassword);
4298 newValidFunc("email", "email", (fieldValidFunc) validateEmail);
4299 newValidFunc("emayl", "email", (fieldValidFunc) validateEmail);
4300 newValidFunc("year", "DoB", (fieldValidFunc) validateDoB);
4301 newValidFunc("month", "DoB", (fieldValidFunc) validateDoB);
4302 newValidFunc("adult", "legal", (fieldValidFunc) validateLegal);
4303 newValidFunc("agree", "legal", (fieldValidFunc) validateLegal);
4304 newValidFunc("aboutMe", "about me", (fieldValidFunc) validateAboutMe);
4305 }
4306 if (NULL == buildPages)
4307 {
4308 buildPages = qhashtbl(0, 0);
4309 newBuildPage("login", (pageBuildFunction) loggedOnPage, (pageBuildFunction) loginPage);
4310 newBuildPage("cancel", (pageBuildFunction) loginPage, (pageBuildFunction) loginPage);
4311 newBuildPage("logout", (pageBuildFunction) loginPage, (pageBuildFunction) loginPage);
4312 newBuildPage("create", (pageBuildFunction) accountCreationPage, (pageBuildFunction) loginPage);
4313 newBuildPage("confirm", (pageBuildFunction) loggedOnPage, (pageBuildFunction) accountCreationPage);
4314 newBuildPage("me", (pageBuildFunction) loggedOnPage, (pageBuildFunction) loginPage);
4315 newBuildPage("update", (pageBuildFunction) loggedOnPage, (pageBuildFunction) loginPage);
4316 newBuildPage("delete", (pageBuildFunction) loginPage, (pageBuildFunction) loginPage);
4317 newBuildPage("list", (pageBuildFunction) listPage, (pageBuildFunction) loginPage);
4318 }
4319 4951
4320 if ('\0' == doit[0]) 4952 if (NULL == accountLevels)
4321 doit = getStrH(Rd->cookies, "doit");
4322 if ('\0' == doit[0])
4323 doit = "logout";
4324 if ('\0' != doit[0])
4325 {
4326 setCookie(Rd, "doit", doit);
4327 Rd->doit = doit;
4328 }
4329
4330 e += validateThings(Rd, doit, "cookies", Rd->cookies);
4331 e += validateThings(Rd, doit, "body", Rd->body);
4332 e += validateThings(Rd, doit, "queries", Rd->queries);
4333 e += validateThings(Rd, doit, "session", Rd->stuff);
4334
4335 if (NULL == Rd->func)
4336 {
4337 buildPage *bp = buildPages->get(buildPages, doit, NULL, false);
4338 if (bp)
4339 {
4340 if (e)
4341 {
4342 Rd->func = bp->eFunc;
4343 E("Got page builder ERROR function for %s, coz of %d errors.", doit, e);
4344 }
4345 else
4346 {
4347 Rd->func = bp->func;
4348 D("Got page builder function for %s.", doit);
4349 }
4350 }
4351 }
4352 if (NULL == Rd->func)
4353 Rd->func = (pageBuildFunction) loginPage;
4354
4355 if (strcmp("https", Rd->Scheme) != 0)
4356 { 4953 {
4357 Rd->Rheaders->putstr (Rd->Rheaders, "Status", "301 Moved Permanently"); 4954 accountLevels = qlisttbl(QLISTTBL_LOOKUPFORWARD | QLISTTBL_THREADSAFE | QLISTTBL_UNIQUE);
4358 Rd->Rheaders->putstrf(Rd->Rheaders, "Location", "https://%s%s", Rd->Host, Rd->RUri); 4955 accountLevels->putstr(accountLevels, "-256", "disabled");
4359 Rd->reply->addstrf(Rd->reply, "<html><title>404 Unknown page</title><head>" 4956 accountLevels->putstr(accountLevels, "-200", "newbie");
4360 "<meta http-equiv='refresh' content='0; URL=https://%s%s' />" 4957 accountLevels->putstr(accountLevels, "-100", "validated");
4361 "</head><body>You should get redirected to <a href='https://%s%s'>https://%s%s</a></body></html>", 4958 accountLevels->putstr(accountLevels, "-50", "vouched for");
4362 Rd->Host, Rd->RUri, Rd->Host, Rd->RUri, Rd->Host, Rd->RUri 4959 accountLevels->putstr(accountLevels, "1", "approved");
4363 ); 4960 accountLevels->putstr(accountLevels, "200", "god");
4364 D("Redirecting dynamic page %s -> https://%s%s", file, Rd->Host, Rd->RUri);
4365 return;
4366 } 4961 }
4367 4962
4368 // Check "Origin" header and /or HTTP_REFERER header. 4963 // Check "Origin" header and /or HTTP_REFERER header.
@@ -4380,7 +4975,7 @@ void account_html(char *file, reqData *Rd, HTMLfile *thisFile)
4380 { 4975 {
4381 bitch(Rd, "Invalid referer.", ref); 4976 bitch(Rd, "Invalid referer.", ref);
4382 D("Invalid referer - %s isn't %s", ref, href); 4977 D("Invalid referer - %s isn't %s", ref, href);
4383 Rd->func = (pageBuildFunction) loginPage; 4978 form = "accountLogin";
4384 } 4979 }
4385 free(href); 4980 free(href);
4386 } 4981 }
@@ -4393,66 +4988,260 @@ void account_html(char *file, reqData *Rd, HTMLfile *thisFile)
4393 { 4988 {
4394 bitch(Rd, "Invalid HOST.", ref); 4989 bitch(Rd, "Invalid HOST.", ref);
4395 D("Invalid HOST - %s isn't %s", ref, href); 4990 D("Invalid HOST - %s isn't %s", ref, href);
4396 Rd->func = (pageBuildFunction) loginPage; 4991 form = "accountLogin";
4992 }
4993
4994 // Redirect to HTTPS if it's HTTP.
4995 if (strcmp("https", Rd->Scheme) != 0)
4996 {
4997 Rd->Rheaders->putstr (Rd->Rheaders, "Status", "301 Moved Permanently");
4998 Rd->Rheaders->putstrf(Rd->Rheaders, "Location", "https://%s%s", Rd->Host, Rd->RUri);
4999 Rd->reply->addstrf(Rd->reply, "<html><title>404 Unknown page</title><head>"
5000 "<meta http-equiv='refresh' content='0; URL=https://%s%s' />"
5001 "</head><body>You should get redirected to <a href='https://%s%s'>https://%s%s</a></body></html>",
5002 Rd->Host, Rd->RUri, Rd->Host, Rd->RUri, Rd->Host, Rd->RUri
5003 );
5004 D("Redirecting dynamic page %s -> https://%s%s", file, Rd->Host, Rd->RUri);
5005 return;
5006 }
5007
5008 // Create the dynamic web pages for account.html.
5009 if (NULL == accountPages)
5010 {
5011 accountPages = qhashtbl(0, 0);
5012
5013
5014 iF = newInputForm("accountAdd", "", NULL, accountAddWeb, accountLoginWeb);
5015 addSession(iF);
5016// fld = addInputField(iF, LUA_TSTRING, "UUID", "UUID", NULL, UUIDValidate, UUIDWeb);
5017// inputFieldExtra(fld, FLD_HIDDEN, 0, 0);
5018 fld = addInputField(iF, LUA_TSTRING, "name", "name", NULL, nameValidate, nameWeb);
5019 inputFieldExtra(fld, FLD_EDITABLE | FLD_REQUIRED, 42, 63);
5020 fld = addInputField(iF, LUA_TPASSWORD, "psswrd", "Re-enter your password",
5021 "Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters.", passwordValidate, passwordWeb);
5022 inputFieldExtra(fld, FLD_EDITABLE, 16, 0);
5023 addEmailFields(iF);
5024 addDoBFields(iF);
5025 addLegalFields(iF);
5026 fld = addInputField(iF, LUA_TSTRING, "ToS", "Terms of Service", "", NULL, ToSWeb);
5027 fld = addInputField(iF, LUA_TSTRING, "aboutMe", "About me", NULL, aboutMeValidate, aboutMeWeb);
5028 inputFieldExtra(fld, FLD_EDITABLE, 50, 16384);
5029 addSubmit(iF, "confirm", "confirm", NULL, accountAdd, "accountView");
5030 addSubmit(iF, "cancel", "cancel", NULL, accountOut, "accountLogin");
5031
5032
5033 iF = newInputForm("accountView", "account view", NULL, accountViewWeb, accountLoginWeb);
5034 addSession(iF);
5035// fld = addInputField(iF, LUA_TSTRING, "UUID", "UUID", NULL, UUIDValidate, UUIDWeb);
5036// inputFieldExtra(fld, FLD_HIDDEN, 0, 0);
5037 fld = addInputField(iF, LUA_TSTRING, "name", "name", NULL, nameValidate, nameWeb);
5038 inputFieldExtra(fld, FLD_HIDDEN, 42, 63);
5039 addSubmit(iF, "login", "", NULL, accountView, "accountView"); // Coz we sometimes want to trigger this from code.
5040 addSubmit(iF, "validate", "", NULL, accountValidate, "accountLogin"); // Coz we sometimes want to trigger this from code.
5041// addSubmit(iF, "edit", "edit", NULL, accountEdit, "accountEdit");
5042// addSubmit(iF, "members", "members", NULL, accountExplore, "accountExplore");
5043 addSubmit(iF, "logout", "logout", NULL, accountOut, "accountLogin");
5044
5045
5046 iF = newInputForm("accountEdit", "account edit", NULL, accountEditWeb, accountLoginWeb);
5047 addSession(iF);
5048// fld = addInputField(iF, LUA_TSTRING, "UUID", "UUID", NULL, UUIDValidate, UUIDWeb);
5049// inputFieldExtra(fld, FLD_HIDDEN, 0, 0);
5050 fld = addInputField(iF, LUA_TSTRING, "name", "name", NULL, nameValidate, nameWeb);
5051 inputFieldExtra(fld, FLD_HIDDEN, 42, 63);
5052 fld = addInputField(iF, LUA_TEMAIL, "email", "email", "", emailValidate, emailWeb);
5053 inputFieldExtra(fld, FLD_NONE, 42, 254);
5054 addSubmit(iF, "login", "", NULL, accountView, "accountView"); // Coz we sometimes want to trigger this from code.
5055 addSubmit(iF, "save", "save", NULL, accountSave, "accountSave");
5056 addSubmit(iF, "cancel", "cancel", NULL, accountOut, "accountView");
5057// addSubmit(iF, "members", "members", NULL, accountExplore, "accountExplore");
5058 addSubmit(iF, "logout", "logout", NULL, accountOut, "accountLogin");
5059// addSubmit(iF, "delete", "delete", NULL, accountDel, "accountDel");
5060
5061
5062 iF = newInputForm("accountLogin", "account login", "Please login, or create your new account.", accountLoginWeb, accountLoginWeb);
5063 addSession(iF);
5064 fld = addInputField(iF, LUA_TSTRING, "name", "name", "Your name needs to be two words, only letters and numbers.", nameValidate, nameWeb);
5065 inputFieldExtra(fld, FLD_EDITABLE | FLD_REQUIRED, 42, 63);
5066 fld = addInputField(iF, LUA_TPASSWORD, "password", "password",
5067 "Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters.", passwordValidate, passwordWeb);
5068 inputFieldExtra(fld, FLD_EDITABLE | FLD_REQUIRED, 16, 0);
5069 addSubmit(iF, "logout", "", NULL, accountOut, "accountLogin"); // Coz we sometimes want to trigger this from code.
5070 addSubmit(iF, "validate", "", NULL, accountValidate, "accountLogin"); // Coz we sometimes want to trigger this from code.
5071 addSubmit(iF, "login", "login", NULL, accountView, "accountView");
5072 addSubmit(iF, "create", "create", NULL, accountCreate, "accountAdd");
5073 }
5074
5075 // Figure out what we are doing.
5076 if ('\0' == form[0])
5077 form = getStrH(Rd->cookies, "form");
5078 if ('\0' == form[0])
5079 form = "accountLogin";
5080 if ('\0' == doit[0])
5081 doit = getStrH(Rd->cookies, "doit");
5082 if ('\0' == doit[0])
5083 doit = "logout";
5084 if ('\0' != doit[0])
5085 {
5086 setCookie(Rd, "doit", doit);
5087 Rd->doit = doit;
4397 } 5088 }
4398 5089
4399 // Redirect to a GET if it was a POST. 5090 iF = accountPages->get(accountPages, form, NULL, false);
4400 if ((0 == e) && (strcmp("POST", Rd->Method) == 0)) 5091 if (NULL == iF)
5092 {
5093 E("No such account page - %s", form);
5094 form = "accountLogin";
5095 doit = "logout";
5096 iF = accountPages->get(accountPages, form, NULL, false);
5097 }
5098 sub = iF->subs->get(iF->subs, doit, NULL, false);
5099 if (NULL == sub)
5100 {
5101 E("No such account action - %s", doit);
5102 form = "accountLogin";
5103 doit = "logout";
5104 iF = accountPages->get(accountPages, form, NULL, false);
5105 sub = iF->subs->get(iF->subs, doit, NULL, false);
5106 }
5107
5108 Rd->doit = doit;
5109 Rd->form = form;
5110 Rd->output = sub->outputForm;
5111
5112 C("Starting dynamic page %s %s -> %s [%s -> %s]", Rd->RUri, form, doit, sub->name, Rd->output);
5113
5114 // Collect the input data.
5115 int count = iF->fields->size(iF->fields);
5116 inputValue *iV = xzalloc(count * sizeof(inputValue));
5117 qlisttbl_obj_t obj;
5118
5119 if (strcmp("cancel", sub->name) != 0)
4401 { 5120 {
4402 if (Rd->func == (pageBuildFunction) loginPage)
4403 freeSesh(Rd, FALSE, TRUE);
4404 5121
4405 if (strcmp("confirm", doit) == 0) 5122// TODO - complain about any extra body or query parameter that we where not expecting.
5123 for (t = 0; t < 3; t++)
5124 i = collectFields(Rd, iF, iV, t);
5125
5126 // Validate the input data.
5127 D("For page %s -> %s, start of validation.", iF->name, sub->name);
5128 t = i;
5129 for (i = 0; i < t; i++)
5130 {
5131 if ((NULL != iV[i].value) || (LUA_TGROUP == iV[i].field->type))
4406 { 5132 {
4407 createUser(Rd); 5133 if (NULL == iV[i].field->validate)
4408 newSesh(Rd, TRUE); 5134 E("No validation function for %s - %s", iF->name, iV[i].field->name);
4409 Rd->chillOut = TRUE; 5135 else
5136 {
5137 if (0 == iV[i].valid)
5138 {
5139 D("Validating %s - %s", iF->name, iV[i].field->name);
5140 int rt = iV[i].field->validate(Rd, iF, &iV[i]);
5141 if (rt)
5142 {
5143 W("Invalidated %s - %s returned %d errors", iF->name, iV[i].field->name, rt);
5144 iV[i].valid = -1;
5145 }
5146 else
5147 {
5148 D("Validated %s - %s", iF->name, iV[i].field->name);
5149 iV[i].valid = 1;
5150 }
5151 e += rt;
5152 if (NULL != iV[i].field->group)
5153 {
5154 // Use the indexes to set the validation result for the other members of the group.
5155 // The assumption is that the validation functions for each are the same, and it validates the members as a group.
5156 for (j = iV[i].index; j > 0; j--)
5157 iV[i + j].valid = iV[i].valid;
5158// TODO - Possible off by one error, but I don't think it matters. Needs more testing.
5159 for (j = 0; j <= iV[i].index; j++)
5160 iV[i + j].valid = iV[i].valid;
5161 }
5162 }
5163 else if (0 > iV[i].valid)
5164 D("Already invalidated %s - %s", iF->name, iV[i].field->name);
5165 else if (0 < iV[i].valid)
5166 D("Already validated %s - %s", iF->name, iV[i].field->name);
5167 }
4410 } 5168 }
5169 doit = Rd->doit;
5170 }
5171
5172 // Submit the data.
5173 sub = iF->subs->get(iF->subs, Rd->doit, NULL, false);
5174//if (strcmp("POST", Rd->Method) == 0) // Not such a good idea, since we are faking a submit for account validation, which is a GET.
5175{
5176 if (0 == e)
5177 {
5178 D("For page %s, start of %s submission.", iF->name, sub->name);
5179 if (NULL != sub->submit)
5180 e += sub->submit(Rd, iF, iV);
5181 }
5182}
5183 free(iV);
4411 5184
4412 if (strcmp("login", doit) == 0) 5185 }
4413 Rd->chillOut = TRUE;
4414 5186
4415 if (Rd->vegOut) 5187 // Return the result.
5188 if (0 == e)
5189 {
5190 if (strcmp("GET", Rd->Method) == 0)
4416 { 5191 {
4417 t("vegOut"); 5192 // Find the output form.
4418 freeSesh(Rd, FALSE, TRUE); 5193 inputForm *oF = accountPages->get(accountPages, Rd->output, NULL, false);
5194 if (NULL == oF)
5195 {
5196 E("No such account page - %s", Rd->output);
5197 form = "accountLogin";
5198 doit = "logout";
5199 oF = accountPages->get(accountPages, form, NULL, false);
5200 }
5201 D("Building output page %s", oF->name);
5202 count = oF->fields->size(oF->fields);
5203 iV = xzalloc(count * sizeof(inputValue));
5204 collectFields(Rd, oF, iV, -1);
5205 collectFields(Rd, oF, iV, 3);
5206 oF->web(Rd, oF, iV);
5207 free(iV);
4419 } 5208 }
4420 else if (Rd->chillOut) 5209 else
4421 { 5210 {
4422 t("chillOut"); 5211 // Redirect to a GET if it was a POST.
4423 freeSesh(Rd, FALSE, FALSE); 5212 if ((strcmp("POST", Rd->Method) == 0))
4424 newSesh(Rd, FALSE); 5213 {
4425 } 5214 if ('\0' != Rd->form[0])
4426 else if ('\0' == Rd->shs.leaf[0]) 5215 setCookie(Rd, "form", Rd->form);
4427 newSesh(Rd, FALSE); 5216 if ('\0' != Rd->doit[0])
4428 5217 setCookie(Rd, "doit", Rd->doit);
4429 Rd->Rheaders->putstr (Rd->Rheaders, "Status", "303 See Other"); 5218 Rd->Rheaders->putstr (Rd->Rheaders, "Status", "303 See Other");
4430 Rd->Rheaders->putstrf(Rd->Rheaders, "Location", "https://%s%s", Rd->Host, Rd->RUri); 5219 Rd->Rheaders->putstrf(Rd->Rheaders, "Location", "https://%s%s", Rd->Host, Rd->RUri);
4431 Rd->reply->addstrf(Rd->reply, "<html><title>Post POST redirect</title><head>" 5220 Rd->reply->addstrf(Rd->reply, "<html><title>Post POST redirect</title><head>"
4432 "<meta http-equiv='refresh' content='0; URL=https://%s%s' />" 5221 "<meta http-equiv='refresh' content='0; URL=https://%s%s' />"
4433 "</head><body>You should get redirected to <a href='https://%s%s'>https://%s%s</a></body></html>", 5222 "</head><body>You should get redirected to <a href='https://%s%s'>https://%s%s</a></body></html>",
4434 Rd->Host, Rd->RUri, Rd->Host, Rd->RUri, Rd->Host, Rd->RUri 5223 Rd->Host, Rd->RUri, Rd->Host, Rd->RUri, Rd->Host, Rd->RUri
4435 ); 5224 );
4436 I("Redirecting dynamic page %s -> https://%s%s", file, Rd->Host, Rd->RUri); 5225 I("Redirecting dynamic page %s -> https://%s%s (%s)", file, Rd->Host, Rd->RUri, Rd->form);
5226 }
5227 }
4437 } 5228 }
4438 else // Actually send the page. 5229 else
4439 { 5230 {
4440 if (Rd->func == (pageBuildFunction) loginPage) 5231 if (0 < e)
4441 { 5232 E("Building output ERROR page %s, coz of %d errors.", iF->name, e);
4442 if (strcmp("confirm", doit) != 0) 5233 else
4443 freeSesh(Rd, FALSE, TRUE); 5234 D("Building alternate output page %s", iF->name);
4444 newSesh(Rd, FALSE); 5235 // First just sort out groups, then get the data from Rd->stuff.
4445 } 5236 count = iF->fields->size(iF->fields);
4446 else if ((0 != e)) // So we can reload details into a broken form, so the user doesn't have to retype everything. 5237 iV = xzalloc(count * sizeof(inputValue));
4447 { 5238 collectFields(Rd, iF, iV, -1);
4448 freeSesh(Rd, FALSE, FALSE); 5239 collectFields(Rd, iF, iV, 3);
4449 newSesh(Rd, FALSE); 5240 iF->eWeb(Rd, iF, iV);
4450 } 5241 free(iV);
4451
4452 Rd->func(Rd, "");
4453 } 5242 }
4454 5243
4455 C("Ending dynamic page %s", file); 5244 C("Ending dynamic page %s %s", Rd->RUri, form);
4456} 5245}
4457 5246
4458 5247
@@ -4470,9 +5259,24 @@ static void cleanup(void)
4470 dbFreeRequest(req); 5259 dbFreeRequest(req);
4471 dbRequests->removefirst(dbRequests); 5260 dbRequests->removefirst(dbRequests);
4472 } 5261 }
4473 if (fieldValidFuncs) fieldValidFuncs->free(fieldValidFuncs); 5262
5263 if (accountPages)
5264 {
5265 qhashtbl_obj_t obj;
5266
5267 memset((void*)&obj, 0, sizeof(obj));
5268 accountPages->lock(accountPages);
5269 while(accountPages->getnext(accountPages, &obj, true) == true)
5270 {
5271// d("%s = %s", obj.name, (char *) obj.data);
5272 }
5273 accountPages->unlock(accountPages);
5274 accountPages->free(accountPages);
5275 }
5276
5277// if (fieldValidFuncs) fieldValidFuncs->free(fieldValidFuncs);
5278// if (buildPages) buildPages->free(buildPages);
4474 if (dynPages) dynPages->free(dynPages); 5279 if (dynPages) dynPages->free(dynPages);
4475 if (buildPages) buildPages->free(buildPages);
4476 if (HTMLfileCache) HTMLfileCache->free(HTMLfileCache); 5280 if (HTMLfileCache) HTMLfileCache->free(HTMLfileCache);
4477 if (HTMLfileCache) mimeTypes->free(mimeTypes); 5281 if (HTMLfileCache) mimeTypes->free(mimeTypes);
4478 if (dbRequests) dbRequests->free(dbRequests); 5282 if (dbRequests) dbRequests->free(dbRequests);
@@ -4491,7 +5295,6 @@ void sledjchisl_main(void)
4491{ 5295{
4492 char *cmd = *toys.optargs; 5296 char *cmd = *toys.optargs;
4493 char *tmp; 5297 char *tmp;
4494 MYSQL *database = NULL, *dbconn = NULL;
4495 gridStats *stats = NULL; 5298 gridStats *stats = NULL;
4496 struct stat statbuf; 5299 struct stat statbuf;
4497 int status, result, i; 5300 int status, result, i;
@@ -4521,14 +5324,14 @@ void sledjchisl_main(void)
4521 } 5324 }
4522 else 5325 else
4523 { 5326 {
4524 if (S_ISREG (statbuf.st_mode)) d("STDIN is a regular file."); 5327 if (S_ISREG (statbuf.st_mode)) D("STDIN is a regular file.");
4525 else if (S_ISDIR (statbuf.st_mode)) d("STDIN is a directory."); 5328 else if (S_ISDIR (statbuf.st_mode)) D("STDIN is a directory.");
4526 else if (S_ISCHR (statbuf.st_mode)) d("STDIN is a character device."); 5329 else if (S_ISCHR (statbuf.st_mode)) D("STDIN is a character device.");
4527 else if (S_ISBLK (statbuf.st_mode)) d("STDIN is a block device."); 5330 else if (S_ISBLK (statbuf.st_mode)) D("STDIN is a block device.");
4528 else if (S_ISFIFO(statbuf.st_mode)) d("STDIN is a FIFO (named pipe)."); 5331 else if (S_ISFIFO(statbuf.st_mode)) D("STDIN is a FIFO (named pipe).");
4529 else if (S_ISLNK (statbuf.st_mode)) d("STDIN is a symbolic link."); 5332 else if (S_ISLNK (statbuf.st_mode)) D("STDIN is a symbolic link.");
4530 else if (S_ISSOCK(statbuf.st_mode)) d("STDIN is a socket."); 5333 else if (S_ISSOCK(statbuf.st_mode)) D("STDIN is a socket.");
4531 else d("STDIN is a unknown file descriptor type."); 5334 else D("STDIN is a unknown file descriptor type.");
4532 if (!S_ISCHR(statbuf.st_mode)) 5335 if (!S_ISCHR(statbuf.st_mode))
4533 isWeb = TRUE; 5336 isWeb = TRUE;
4534 } 5337 }
@@ -4543,7 +5346,7 @@ void sledjchisl_main(void)
4543 int ngroups; 5346 int ngroups;
4544 5347
4545 pw = xgetpwuid(euid); 5348 pw = xgetpwuid(euid);
4546 D("Running as user %s", pw->pw_name); 5349 I("Running as user %s", pw->pw_name);
4547 5350
4548 grp = xgetgrgid(egid); 5351 grp = xgetgrgid(egid);
4549 ngroups = getgroups(i, groups); 5352 ngroups = getgroups(i, groups);
@@ -4631,20 +5434,20 @@ jit library is loaded or the JIT compiler will not be activated.
4631 } 5434 }
4632 } 5435 }
4633 DEBUG = configs->getint(configs, "debug"); 5436 DEBUG = configs->getint(configs, "debug");
4634 d("Setting DEBUG = %d", DEBUG); 5437 D("Setting DEBUG = %d", DEBUG);
4635 if ((vd = configs->get (configs, "loadAverageInc", NULL, false)) != NULL) {loadAverageInc = *((float *) vd); d("Setting loadAverageInc = %f", loadAverageInc);} 5438 if ((vd = configs->get (configs, "loadAverageInc", NULL, false)) != NULL) {loadAverageInc = *((float *) vd); D("Setting loadAverageInc = %f", loadAverageInc);}
4636 if ((vd = configs->get (configs, "simTimeOut", NULL, false)) != NULL) {simTimeOut = (int) *((float *) vd); d("Setting simTimeOut = %d", simTimeOut);} 5439 if ((vd = configs->get (configs, "simTimeOut", NULL, false)) != NULL) {simTimeOut = (int) *((float *) vd); D("Setting simTimeOut = %d", simTimeOut);}
4637 if ((tmp = configs->getstr(configs, "scRoot", false)) != NULL) {scRoot = tmp; d("Setting scRoot = %s", scRoot);} 5440 if ((tmp = configs->getstr(configs, "scRoot", false)) != NULL) {scRoot = tmp; D("Setting scRoot = %s", scRoot);}
4638 if ((tmp = configs->getstr(configs, "scUser", false)) != NULL) {scUser = tmp; d("Setting scUser = %s", scUser);} 5441 if ((tmp = configs->getstr(configs, "scUser", false)) != NULL) {scUser = tmp; D("Setting scUser = %s", scUser);}
4639 if ((tmp = configs->getstr(configs, "Tconsole", false)) != NULL) {Tconsole = tmp; d("Setting Tconsole = %s", Tconsole);} 5442 if ((tmp = configs->getstr(configs, "Tconsole", false)) != NULL) {Tconsole = tmp; D("Setting Tconsole = %s", Tconsole);}
4640 if ((tmp = configs->getstr(configs, "Tsocket", false)) != NULL) {Tsocket = tmp; d("Setting Tsocket = %s", Tsocket);} 5443 if ((tmp = configs->getstr(configs, "Tsocket", false)) != NULL) {Tsocket = tmp; D("Setting Tsocket = %s", Tsocket);}
4641 if ((tmp = configs->getstr(configs, "Ttab", false)) != NULL) {Ttab = tmp; d("Setting Ttab = %s", Ttab);} 5444 if ((tmp = configs->getstr(configs, "Ttab", false)) != NULL) {Ttab = tmp; D("Setting Ttab = %s", Ttab);}
4642 if ((tmp = configs->getstr(configs, "webRoot", false)) != NULL) {webRoot = tmp; d("Setting webRoot = %s", webRoot);} 5445 if ((tmp = configs->getstr(configs, "webRoot", false)) != NULL) {webRoot = tmp; D("Setting webRoot = %s", webRoot);}
4643 if ((tmp = configs->getstr(configs, "URL", false)) != NULL) {URL = tmp; d("Setting URL = %s", URL);} 5446 if ((tmp = configs->getstr(configs, "URL", false)) != NULL) {URL = tmp; D("Setting URL = %s", URL);}
4644 if ((vd = configs->get (configs, "seshTimeOut", NULL, false)) != NULL) {seshTimeOut = (int) *((float *) vd); d("Setting seshTimeOut = %d", seshTimeOut);} 5447 if ((vd = configs->get (configs, "seshTimeOut", NULL, false)) != NULL) {seshTimeOut = (int) *((float *) vd); D("Setting seshTimeOut = %d", seshTimeOut);}
4645 if ((vd = configs->get (configs, "idleTimeOut", NULL, false)) != NULL) {idleTimeOut = (int) *((float *) vd); d("Setting idleTimeOut = %d", idleTimeOut);} 5448 if ((vd = configs->get (configs, "idleTimeOut", NULL, false)) != NULL) {idleTimeOut = (int) *((float *) vd); D("Setting idleTimeOut = %d", idleTimeOut);}
4646 if ((vd = configs->get (configs, "newbieTimeOut", NULL, false)) != NULL) {newbieTimeOut = (int) *((float *) vd); d("Setting newbieTimeOut = %d", newbieTimeOut);} 5449 if ((vd = configs->get (configs, "newbieTimeOut", NULL, false)) != NULL) {newbieTimeOut = (int) *((float *) vd); D("Setting newbieTimeOut = %d", newbieTimeOut);}
4647 if ((tmp = configs->getstr(configs, "ToS", false)) != NULL) {ToS = tmp; d("Setting ToS = %s", ToS);} 5450 if ((tmp = configs->getstr(configs, "ToS", false)) != NULL) {ToS = tmp; D("Setting ToS = %s", ToS);}
4648 5451
4649 5452
4650 // Use a FHS compatible setup - 5453 // Use a FHS compatible setup -
@@ -4824,19 +5627,8 @@ jit library is loaded or the JIT compiler will not be activated.
4824 } 5627 }
4825 else 5628 else
4826 { 5629 {
4827 dbconn = mysql_real_connect(database, 5630 if (!dbConnect())
4828 getStrH(configs, "Data Source"),
4829 getStrH(configs, "User ID"),
4830 getStrH(configs, "Password"),
4831 getStrH(configs, "Database"),
4832// 3036, "/var/run/mysqld/mysqld.sock",
4833 0, NULL,
4834 CLIENT_FOUND_ROWS | CLIENT_LOCAL_FILES | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS);
4835 if (NULL == dbconn)
4836 {
4837 E("mysql_real_connect() failed - %s", mysql_error(database));
4838 goto finished; 5631 goto finished;
4839 }
4840 5632
4841 // Need to kick this off. 5633 // Need to kick this off.
4842 stats = getStats(database, stats); 5634 stats = getStats(database, stats);
@@ -4867,11 +5659,11 @@ jit library is loaded or the JIT compiler will not be activated.
4867 I("Running SledjChisl inside a web server, pid %d.", getpid()); 5659 I("Running SledjChisl inside a web server, pid %d.", getpid());
4868 5660
4869 if (0 == toys.optc) 5661 if (0 == toys.optc)
4870 d("no args"); 5662 D("no args");
4871 else 5663 else
4872 { 5664 {
4873 for (entries = 0, bytes = -1; entries < toys.optc; entries++) 5665 for (entries = 0, bytes = -1; entries < toys.optc; entries++)
4874 d("ARG %s\n", toys.optargs[entries]); 5666 D("ARG %s\n", toys.optargs[entries]);
4875 } 5667 }
4876 printEnv(environ); 5668 printEnv(environ);
4877 5669
@@ -4967,32 +5759,40 @@ if ((strcmp("HTTP_COOKIE", ky) == 0) || (strcmp("CONTENT_LENGTH", ky) == 0) || (
4967t("COOKIES"); 5759t("COOKIES");
4968 Rd->cookies = toknize(Rd->headers->getstr(Rd->headers, "HTTP_COOKIE", false), "=;"); 5760 Rd->cookies = toknize(Rd->headers->getstr(Rd->headers, "HTTP_COOKIE", false), "=;");
4969 santize(Rd->cookies, TRUE); 5761 santize(Rd->cookies, TRUE);
5762 if (strcmp("GET", Rd->Method) == 0)
5763 { // In theory a POST has body fields INSTEAD of query fields. Especially for ignoring the linky-hashish after the validation page.
4970t("QUERY"); 5764t("QUERY");
4971 Rd->queries = toknize(Rd->headers->getstr(Rd->headers, "QUERY_STRING", false), "=&"); 5765 Rd->queries = toknize(Rd->headers->getstr(Rd->headers, "QUERY_STRING", false), "=&");
4972 santize(Rd->queries, TRUE); 5766 santize(Rd->queries, TRUE);
5767 }
5768 else
5769 {
5770T("ignoring QUERY");
5771 Rd->queries = qhashtbl(0, 0);
5772// TODO - stop this from leaking.
5773 Rd->RUri = xmprintf("%s%s", Rd->Script, Path);
5774 }
5775t("BODY");
4973 char *Body = NULL; 5776 char *Body = NULL;
4974 if (Length != NULL) 5777 if (Length != NULL)
4975 { 5778 {
4976 size_t len = strtol(Length, NULL, 10); 5779 size_t len = strtol(Length, NULL, 10);
4977 Body = xmalloc(len + 1); 5780 Body = xmalloc(len + 1);
4978 int c = FCGI_fread(Body, 1, len, FCGI_stdin); 5781 int c = FCGI_fread(Body, 1, len, FCGI_stdin);
4979 if (c != len) 5782 if (c != len)
4980 { 5783 {
4981 E("Tried to read %d of the body, only got %d!", len, c); 5784 E("Tried to read %d of the body, only got %d!", len, c);
4982 } 5785 }
4983 Body[len] = '\0'; 5786 Body[len] = '\0';
4984 } 5787 }
4985 else 5788 else
4986 Length = "0"; 5789 Length = "0";
4987t("BODY");
4988 Rd->body = toknize(Body, "=&"); 5790 Rd->body = toknize(Body, "=&");
4989 free(Body); 5791 free(Body);
4990 santize(Rd->body, TRUE); 5792 santize(Rd->body, TRUE);
4991 5793
4992 5794 I("%s %s://%s%s -> %s%s", Rd->Method, Rd->Scheme, Rd->Host, Rd->RUri, webRoot, Path);
4993 I("Started FCGI web %s request ROLE = %s, body is %s bytes, pid %d.", Rd->Method, Role, Length, getpid()); 5795 D("Started FCGI web request ROLE = %s, body is %s bytes, pid %d.", Role, Length, getpid());
4994 D("%s://%s%s -> %s%s", Rd->Scheme, Rd->Host, Rd->RUri, webRoot, Path);
4995
4996 5796
4997/* TODO - other headers may include - 5797/* TODO - other headers may include -
4998 different Content-type 5798 different Content-type
@@ -5037,7 +5837,7 @@ t("BODY");
5037 E("Failed to open %s, it's not a virtual file either", toybuf); 5837 E("Failed to open %s, it's not a virtual file either", toybuf);
5038 goto sendReply; 5838 goto sendReply;
5039 } 5839 }
5040 I("Dynamic page %s found.", dp->name); 5840// I("Dynamic page %s found.", dp->name);
5041 dp->func(toybuf, Rd, thisFile); 5841 dp->func(toybuf, Rd, thisFile);
5042 char *finl = Rd->reply->tostring(Rd->reply); // This mallocs new storage and returns it to us. 5842 char *finl = Rd->reply->tostring(Rd->reply); // This mallocs new storage and returns it to us.
5043// TODO - maybe cache this? 5843// TODO - maybe cache this?
@@ -5164,6 +5964,7 @@ fcgiDone:
5164 qhashtbl_free(Rd->cookies); 5964 qhashtbl_free(Rd->cookies);
5165 qhashtbl_free(Rd->body); 5965 qhashtbl_free(Rd->body);
5166 qhashtbl_free(Rd->queries); 5966 qhashtbl_free(Rd->queries);
5967 if (Rd->lnk) free(Rd->lnk);
5167 5968
5168 struct timespec now; 5969 struct timespec now;
5169 if (-1 == clock_gettime(CLOCK_REALTIME, &now)) 5970 if (-1 == clock_gettime(CLOCK_REALTIME, &now))
@@ -5240,8 +6041,8 @@ fcgiDone:
5240 free(d); 6041 free(d);
5241 } 6042 }
5242 6043
5243 for (i = 0; i < sims->num; i++) 6044// for (i = 0; i < sims->num; i++)
5244// for (i = 0; i < 2; i++) 6045 for (i = 0; i < 2; i++)
5245 { 6046 {
5246 char *sim = sims->sims[i], *name = getSimName(sims->sims[i]); 6047 char *sim = sims->sims[i], *name = getSimName(sims->sims[i]);
5247 6048