(m_Config, "HomeURI", new string[] { "Startup", "Hypergrid" }, String.Empty);
- ssi.Add("grid", cfg.GetString("gridname", "my grid"));
- ssi.Add("uri", cfg.GetString("login", HomeURI));
- ssi.Add("version", VersionInfo.Version);
- server.AddHTTPHandler("/web/", WebRequestHandler);
- }
-
- private Hashtable WebRequestHandler(Hashtable request)
- {
- long locIn = m_database.Count("Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
- long HGin = m_database.Count("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
- long locOut = m_database.Count("hg_traveling_data", "GridExternalName != '" + ssi["uri"] + "'"); // Locals that are HGing.
- Hashtable reply = new Hashtable();
- ssi["members"] = m_database.Count("UserAccounts").ToString();
- ssi["sims"] = m_database.Count("regions").ToString();
- ssi["inworld"] = (locIn - HGin).ToString();
- ssi["outworld"] = locOut.ToString();
- ssi["hgers"] = HGin.ToString();
- ssi["month"] = m_database.Count("GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))").ToString();
-
- string reqpath = (string) request["uri"];
- string[] query = (string[]) request["querystringkeys"];
- Hashtable headers = (Hashtable) request["headers"];
- string method = (string) request["http-method"];
- string type = (string) request["content-type"];
- string body = (string) request["body"];
- string file = reqpath.Remove(0, 5);
- string path = Path.Combine(Util.webDir(), file);
-
- m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} type {2} body {3}.", method, reqpath, type, body);
- foreach (DictionaryEntry h in headers)
- m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
- foreach (String q in query)
- m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} query {2} value {3}", method, reqpath, q, (string) request[q]);
-
- reply["int_response_code"] = 200;
- if ("GET" == method)
- {
- if (File.Exists(path))
- {
- string m = (string) mime[Path.GetExtension(path).ToLower()];
- reply["content_type"] = m;
- if ((null == m) || ("text/" != m.Substring(0, 5)))
- reply["bin_response_data"] = File.ReadAllBytes(path);
- else
- {
- StreamReader csr = File.OpenText(path);
- string content = csr.ReadToEnd();
- // Slow and wasteful, but I'm expecting only tiny web files, not accessed very often.
- foreach (DictionaryEntry v in ssi)
- {
- content = content.Replace("", (string) v.Value);
- }
- reply["str_response_string"] = content;
- csr.Close();
- }
- }
- else
- {
- if ("account.html" == file)
- reply["str_response_string"] = loginPage(null, "");
- else
- {
- m_log.ErrorFormat("[WEB SERVICE]: Unable to read {0}.", path);
- reply["int_response_code"] = 404;
- reply["content_type"] = "text/html";
- reply["str_response_string"] = "404 Unknown page " +
- "404 error, can't find the " + reqpath + " page.
";
- }
- }
- }
- else if ("POST" == method)
- {
- Hashtable fields = new Hashtable();
- string[] bdy = body.Split('&');
- body = "";
- foreach (String bd in bdy)
- {
- string[] b = bd.Split('=');
- if (b.Length == 0)
- continue;
- String n = System.Web.HttpUtility.UrlDecode(b[0]);
- String v = "";
- if (b.Length > 1)
- v = System.Web.HttpUtility.UrlDecode(b[1]);
- fields[n] = v;
- body = body + "" + n + " = " + v + "
\n";
- }
-
- if ("account.html" == file)
- {
- if ("logout" == fields["doit"].ToString())
- reply["str_response_string"] = loginPage(null, "Logged out.");
- else if ("create" == fields["doit"].ToString())
- {
- if ("" == fields["email"].ToString())
- reply["str_response_string"] = loginPage(fields, "Please supply an email address when creating an account.");
- else
- {
- reply["str_response_string"] = loggedOnPage(body, fields);
- }
- }
- else if ("list" == fields["doit"].ToString())
- {
- List< Hashtable > rows = m_database.Select("UserAccounts",
- "CONCAT(FirstName,' ',LastName) as Name,UserTitle as Title,UserLevel as Level,UserFlags as Flags,PrincipalID as UUID",
- "", "Name");
- reply["str_response_string"] = "member accounts " +
- table(rows, new string[5] {"Name", "Title", "Level", "Flags", "UUID"}, "member accounts",
- "account.html?doit=edit&token=" + fields["token"].ToString(), "UUID") + "" + button("my account") + "
";
- }
- else
- {
- reply["str_response_string"] = loggedOnPage(body, fields);
- }
- }
- else
- {
- m_log.ErrorFormat("[WEB SERVICE]: No such POST target {0}.", path);
- reply["int_response_code"] = 404;
- reply["content_type"] = "text/html";
- reply["str_response_string"] = "404 Unknown page " +
- "404 error, can't find the " + reqpath + " page.
";
- }
- }
- else
- {
- m_log.ErrorFormat("[WEB SERVICE]: UNKNOWN method {0} path {1}.", method, reqpath);
- reply["int_response_code"] = 404;
- reply["content_type"] = "text/html";
- reply["str_response_string"] = "Unknown method " +
- "HUH! For " + reqpath + " page.
";
- }
-
- m_log.Info("[WEB SERVICE]: ");
- return reply;
- }
-
- private string loginPage(Hashtable fields, string message)
- {
- string f = "";
- string l = "";
- string e = "";
- if (null != fields)
- {
- f = fields["firstName"].ToString();
- l = fields["lastName"].ToString();
- e = fields["email"].ToString();
- }
- return header(ssi["grid"] + " account")
- + form("account.html", "",
- text("text", "first name", "firstName", f, 16, true)
- + text("text", "last name", "lastName", l, 16, true)
- + text("email", "email", "email", e, 0, false)
- + text("password", "password", "password", "", 14,true)
- + button("create")
- + button("login")
- )
- + "" + message + "
"
- + footer();
- }
-
- private string loggedOnPage(string body, Hashtable fields)
- {
- return header(ssi["grid"] + " account")
- + "" + ssi["grid"] + " account for " + fields["firstName"].ToString() + " " + fields["lastName"].ToString() + " "
- + form("account.html", fields["token"].ToString(),
- hidden("firstName", fields["firstName"].ToString())
- + hidden("lastName", fields["lastName"].ToString())
-// + hidden("UUID", fields["UUID"].ToString())
- + text("email", "email", "email", fields["email"].ToString(), 0, false)
- + text("password", "password", "password", "", 14, false)
-// + text("title", "text", "title", fields["title"].ToString(), 0, false)
- + select("type", "type",
- option("", false)
- + option("approved", true)
- + option("disabled", false)
- + option("god", false)
- )
- + button("delete")
- + button("list")
- + button("logout")
-// + button("read")
- + button("update")
- )
- + body
- + footer();
- }
-
- private string header(string title)
- {
- return "\n \n " + title + " \n \n \n";
- }
-
- private string table(List< Hashtable > rows, string[] fields, string caption, string URL, string id)
- {
- string tbl = "" + caption + " ";
- bool head = true;
- string address = "";
- string addrend = "";
- foreach (Hashtable row in rows)
- {
- if (0 == fields.Length)
- {
- int c = 0;
- foreach (DictionaryEntry r in row)
- c++;
- fields = new string[c];
- c = 0;
- foreach (DictionaryEntry r in row)
- fields[c++] = (string) r.Key;
- }
- string line = "";
- address = "";
- if ("" != URL)
- {
- address = "";
- if (head)
- {
- foreach (string s in fields)
- line = line + " " + s + " ";
- tbl = tbl + line + " \n";
- head = false;
- }
- line = "";
- foreach (string s in fields)
- {
- if (s == id)
- line = line + "" + address + row[s] + addrend + " ";
- else
- line = line + "" + row[s] + " ";
- }
- tbl = tbl + line + " \n";
- }
- return tbl + "
";
- }
-
- private string form(string action, string token, string form)
- {
- return " \n";
- }
-
- private string hidden(string name, string val)
- {
- return " \n";
- }
-
- private string text(string type, string title, string name, string val, int max, bool required)
- {
- string extra = "";
- if (0 < max)
- extra = extra + " maxlength=\"" + max.ToString() + "\"";
- if (required)
- extra = extra + " required";
- if ("" != val)
- val = "value=\"" + val + "\"";
- return " " + title + " :
\n";
- }
-
- private string select(string title, string name, string options)
- {
- return " " + title + " : \n \n" + options + " \n
\n";
- }
-
- private string option(string title, bool selected)
- {
- string sel = "";
- if (selected)
- sel = " selected";
- return " " + title + " \n";
- }
-
- private string button(string title)
- {
- return " " + title + " \n";
- }
-
- private string footer()
- {
- return " \n\n";
- }
-
- }
-}
--
cgit v1.1
From 70efa042176d2dd1c49f9f00a6e5b9452bbc9de6 Mon Sep 17 00:00:00 2001
From: onefang
Date: Tue, 13 Aug 2019 07:35:50 +1000
Subject: Beef up the web server a bit.
Sanatize the path.
Add support for Last-Modified, If-Modified-Since, and Cache-Control:
no-cache.
Teach the base server about more binary content types.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 37 +++++++++++++++++++++++
1 file changed, 37 insertions(+)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index 2607386..06fd892 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -91,6 +91,7 @@ namespace OpenSim.Server.Handlers.Web
long HGin = m_database.Count("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
long locOut = m_database.Count("hg_traveling_data", "GridExternalName != '" + ssi["uri"] + "'"); // Locals that are HGing.
Hashtable reply = new Hashtable();
+ Hashtable replyHeaders = new Hashtable();
ssi["members"] = m_database.Count("UserAccounts").ToString();
ssi["sims"] = m_database.Count("regions").ToString();
ssi["inworld"] = (locIn - HGin).ToString();
@@ -107,6 +108,16 @@ namespace OpenSim.Server.Handlers.Web
string file = reqpath.Remove(0, 5);
string path = Path.Combine(Util.webDir(), file);
+ if (! Path.GetFullPath(path).StartsWith(Path.GetFullPath(Util.webDir())))
+ {
+ m_log.ErrorFormat("[WEB SERVICE]: INVALID PATH {0} != {1}", Path.GetFullPath(path), Path.GetFullPath(Util.webDir()));
+ reply["int_response_code"] = 404;
+ reply["content_type"] = "text/html";
+ reply["str_response_string"] = "404 Unknown page " +
+ "404 error, can't find the " + reqpath + " page.
";
+ return reply;
+ }
+
m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} type {2} body {3}.", method, reqpath, type, body);
foreach (DictionaryEntry h in headers)
m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
@@ -118,12 +129,36 @@ namespace OpenSim.Server.Handlers.Web
{
if (File.Exists(path))
{
+ DateTime dt = File.GetLastWriteTimeUtc(path);
string m = (string) mime[Path.GetExtension(path).ToLower()];
reply["content_type"] = m;
if ((null == m) || ("text/" != m.Substring(0, 5)))
+ {
+ string ifdtr = (string) headers["if-modified-since"];
+ if (null != ifdtr)
+ {
+ try
+ {
+ DateTime ifdt = DateTime.Parse(ifdtr, System.Globalization.CultureInfo.InvariantCulture);
+ if (0 >= DateTime.Compare(ifdt, dt))
+ {
+ reply["int_response_code"] = 304;
+ m_log.InfoFormat("[WEB SERVICE]: If-Modified-Since is earliar or equal to Last-Modified, from {0}", path);
+ reply["headers"] = replyHeaders;
+ return reply;
+ }
+ }
+ catch (Exception)
+ {
+ m_log.ErrorFormat("[WEB SERVICE]: Invalid If-Modified-Since header, ignoring it, from {0}", path);
+ }
+ }
+ replyHeaders["Last-Modified"] = dt.ToString("R");
reply["bin_response_data"] = File.ReadAllBytes(path);
+ }
else
{
+ replyHeaders["Cache-Control"] = "no-cache";
StreamReader csr = File.OpenText(path);
string content = csr.ReadToEnd();
// Slow and wasteful, but I'm expecting only tiny web files, not accessed very often.
@@ -169,6 +204,7 @@ namespace OpenSim.Server.Handlers.Web
if ("account.html" == file)
{
+ replyHeaders["Cache-Control"] = "no-cache";
if ("logout" == fields["doit"].ToString())
reply["str_response_string"] = loginPage(null, "Logged out.");
else if ("create" == fields["doit"].ToString())
@@ -213,6 +249,7 @@ namespace OpenSim.Server.Handlers.Web
}
m_log.Info("[WEB SERVICE]: ");
+ reply["headers"] = replyHeaders;
return reply;
}
--
cgit v1.1
From 9c043cbf4c9adb85acef5c9ffa269d4180f44832 Mon Sep 17 00:00:00 2001
From: onefang
Date: Sat, 17 Aug 2019 07:05:49 +1000
Subject: Simple web server gets HTTPS.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index 06fd892..3a8e906 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -12,6 +12,7 @@ using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Data.MySQL;
using OpenSim.Framework;
+using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Server.Handlers.Base;
@@ -82,8 +83,20 @@ namespace OpenSim.Server.Handlers.Web
ssi.Add("grid", cfg.GetString("gridname", "my grid"));
ssi.Add("uri", cfg.GetString("login", HomeURI));
ssi.Add("version", VersionInfo.Version);
+
server.AddHTTPHandler("/web/", WebRequestHandler);
- }
+ IConfig networkConfig = m_Config.Configs["Network"];
+ if (null != networkConfig)
+ {
+ uint https_port = (uint) networkConfig.GetInt("https_port", 0);
+ if (0 != https_port)
+ {
+ server = MainServer.GetHttpServer(https_port, null);
+ if (null != server)
+ server.AddHTTPHandler("/web/", WebRequestHandler);
+ }
+ }
+ }
private Hashtable WebRequestHandler(Hashtable request)
{
--
cgit v1.1
From ee33dd4b56f7782bbf7798fa3ff8821ff81acd36 Mon Sep 17 00:00:00 2001
From: onefang
Date: Sat, 17 Aug 2019 07:09:16 +1000
Subject: VArious additions to account manager.
First and last names merged into one name.
Is name two words check, a suprisingly effective spam blocker.
Poor mans Bobby Tables protection.
Various other input checking.
Added account creation confirmation page.
Some text and length tweaks.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 102 ++++++++++++++++++----
1 file changed, 83 insertions(+), 19 deletions(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index 3a8e906..7879e4e 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -6,6 +6,7 @@ using System.Net;
using System.Reflection;
using System.Security;
using System.Text;
+using System.Text.RegularExpressions;
using log4net;
using Nini.Config;
using OpenMetaverse;
@@ -131,13 +132,14 @@ namespace OpenSim.Server.Handlers.Web
return reply;
}
- m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} type {2} body {3}.", method, reqpath, type, body);
+ m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} contont type {2} body {3}.", method, reqpath, type, body);
foreach (DictionaryEntry h in headers)
m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
foreach (String q in query)
m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} query {2} value {3}", method, reqpath, q, (string) request[q]);
reply["int_response_code"] = 200;
+// TODO - need to support HEAD method, seems to be what triggers the endles GETs.
if ("GET" == method)
{
if (File.Exists(path))
@@ -211,25 +213,71 @@ namespace OpenSim.Server.Handlers.Web
String v = "";
if (b.Length > 1)
v = System.Web.HttpUtility.UrlDecode(b[1]);
+ if ((0 != String.Compare("password", n)) && (0 != String.Compare("psswrd", n)))
+ {
+ // Poor mans Bobby Tables protection.
+ v = v.Replace("'", "_");
+ v = v.Replace("\"", "_");
+ v = v.Replace(";", "_");
+ v = v.Replace("(", "_");
+ v = v.Replace(")", "_");
+ }
fields[n] = v;
body = body + "" + n + " = " + v + "
\n";
}
if ("account.html" == file)
{
+ string doit = fields["doit"].ToString();
replyHeaders["Cache-Control"] = "no-cache";
- if ("logout" == fields["doit"].ToString())
+ if ("logout" == doit)
reply["str_response_string"] = loginPage(null, "Logged out.");
- else if ("create" == fields["doit"].ToString())
+ else if (("create" == doit) || ("confirm" == doit))
{
+ Regex rgxName = new Regex("^[a-zA-Z0-9]+$");
+ Regex rgxEmail = new Regex("^.+@.+\\..+$");
+ string[] names = fields["name"].ToString().Split(' ');
if ("" == fields["email"].ToString())
reply["str_response_string"] = loginPage(fields, "Please supply an email address when creating an account.");
+ else if (!rgxEmail.IsMatch(fields["email"].ToString()))
+ reply["str_response_string"] = loginPage(fields, "Please supply a valid email address when creating an account.");
+ else if (!Uri.IsWellFormedUriString("mailto:" + fields["email"].ToString(), System.UriKind.Absolute))
+ reply["str_response_string"] = loginPage(fields, "Please supply a valid email address when creating an account.");
+// TODO - the other test to do here is actually lookup the domain name, looking for any sort of record.
+ else if (2 != names.Length)
+ reply["str_response_string"] = loginPage(fields, "Please supply a two word name when creating an account.");
+ // SL docs say 31 characters each for first and last name. UserAccounts table is varchar(64) each. userinfo has varchar(50) for the combined name.
+ // The userinfo table seems to be obsolete.
+ // Singularity at least limits the total name to 64.
+ // I can't find any limitations on characters allowed, but I only ever see letters and digits used. Case is stored, but not significant.
+ // OpenSims "create user" console command doesn't sanitize it at all, even crashing on some names.
+ else if (31 < names[0].Length)
+ reply["str_response_string"] = loginPage(fields, "First and last names are limited to 31 letters each.");
+ else if (31 < names[1].Length)
+ reply["str_response_string"] = loginPage(fields, "First and last names are limited to 31 letters each.");
+ else if (!rgxName.IsMatch(names[0]))
+ reply["str_response_string"] = loginPage(fields, "First and last names are limited to letters and digits.");
+ else if (!rgxName.IsMatch(names[1]))
+ reply["str_response_string"] = loginPage(fields, "First and last names are limited to letters and digits.");
+// TODO - check and disallow god names, those are done in the console.
else
{
- reply["str_response_string"] = loggedOnPage(body, fields);
+ if (("create" == doit))
+ reply["str_response_string"] = accountCreationPage(body, fields);
+ else
+ {
+ if (0 != String.Compare(fields["psswrd"].ToString(), fields["password"].ToString()))
+ reply["str_response_string"] = loginPage(fields, "Passwords are not the same.");
+ else
+ reply["str_response_string"] = loggedOnPage(body, fields);
+ }
}
}
- else if ("list" == fields["doit"].ToString())
+ else if ("cancel" == doit)
+ {
+ reply["str_response_string"] = loginPage(null, "Cancelled.");
+ }
+ else if ("list" == doit)
{
List< Hashtable > rows = m_database.Select("UserAccounts",
"CONCAT(FirstName,' ',LastName) as Name,UserTitle as Title,UserLevel as Level,UserFlags as Flags,PrincipalID as UUID",
@@ -268,21 +316,19 @@ namespace OpenSim.Server.Handlers.Web
private string loginPage(Hashtable fields, string message)
{
- string f = "";
- string l = "";
+ string n = "";
string e = "";
if (null != fields)
{
- f = fields["firstName"].ToString();
- l = fields["lastName"].ToString();
+ n = fields["name"].ToString();
e = fields["email"].ToString();
}
return header(ssi["grid"] + " account")
+ form("account.html", "",
- text("text", "first name", "firstName", f, 16, true)
- + text("text", "last name", "lastName", l, 16, true)
- + text("email", "email", "email", e, 0, false)
- + text("password", "password", "password", "", 14,true)
+ text("text", "name", "name", n, 63, true)
+ + text("email", "email", "email", e, 254, false)
+ + text("password", "password", "password", "", 0, true)
+ + "Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters."
+ button("create")
+ button("login")
)
@@ -290,17 +336,35 @@ namespace OpenSim.Server.Handlers.Web
+ footer();
}
+ private string accountCreationPage(string body, Hashtable fields)
+ {
+ return header(ssi["grid"] + " account")
+ + "Creating " + ssi["grid"] + " account for " + fields["name"].ToString() + " "
+ + form("account.html", fields["token"].ToString(),
+ hidden("name", fields["name"].ToString())
+ + hidden("psswrd", fields["password"].ToString())
+ + text("email", "An email will be sent to", "email", fields["email"].ToString(), 254, true)
+ + " to validate it, please double check this."
+ + text("password", "Re-enter your password", "password", "", 0, true)
+ + "Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters."
+ + button("confirm")
+ + button("cancel")
+ )
+ + body
+ + footer();
+ }
+
private string loggedOnPage(string body, Hashtable fields)
{
return header(ssi["grid"] + " account")
- + "" + ssi["grid"] + " account for " + fields["firstName"].ToString() + " " + fields["lastName"].ToString() + " "
+ + "" + ssi["grid"] + " account for " + fields["name"].ToString() + " "
+ form("account.html", fields["token"].ToString(),
- hidden("firstName", fields["firstName"].ToString())
- + hidden("lastName", fields["lastName"].ToString())
+ hidden("name", fields["name"].ToString())
// + hidden("UUID", fields["UUID"].ToString())
- + text("email", "email", "email", fields["email"].ToString(), 0, false)
- + text("password", "password", "password", "", 14, false)
-// + text("title", "text", "title", fields["title"].ToString(), 0, false)
+ + text("email", "email", "email", fields["email"].ToString(), 254, true)
+ + text("password", "password", "password", "", 0, false)
+ + "Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters."
+// + text("title", "text", "title", fields["title"].ToString(), 64, false)
+ select("type", "type",
option("", false)
+ option("approved", true)
--
cgit v1.1
From 7ed68c3e38b7e1b46aa110d4eb1b1eac686f291e Mon Sep 17 00:00:00 2001
From: onefang
Date: Sat, 17 Aug 2019 08:38:45 +1000
Subject: Implement HEAD in our simple web server.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index 7879e4e..c18e09e 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -139,8 +139,7 @@ namespace OpenSim.Server.Handlers.Web
m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} query {2} value {3}", method, reqpath, q, (string) request[q]);
reply["int_response_code"] = 200;
-// TODO - need to support HEAD method, seems to be what triggers the endles GETs.
- if ("GET" == method)
+ if (("GET" == method) || ("HEAD" == method))
{
if (File.Exists(path))
{
@@ -160,6 +159,11 @@ namespace OpenSim.Server.Handlers.Web
reply["int_response_code"] = 304;
m_log.InfoFormat("[WEB SERVICE]: If-Modified-Since is earliar or equal to Last-Modified, from {0}", path);
reply["headers"] = replyHeaders;
+ if ("HEAD" == method)
+ {
+ reply["bin_response_data"] = null;
+ reply["str_response_string"] = null;
+ }
return reply;
}
}
@@ -311,6 +315,11 @@ namespace OpenSim.Server.Handlers.Web
m_log.Info("[WEB SERVICE]: ");
reply["headers"] = replyHeaders;
+ if ("HEAD" == method)
+ {
+ reply["bin_response_data"] = null;
+ reply["str_response_string"] = null;
+ }
return reply;
}
--
cgit v1.1
From 8f280962f019d46e0367b29246283a1e34ceb955 Mon Sep 17 00:00:00 2001
From: onefang
Date: Sun, 18 Aug 2019 14:12:21 +1000
Subject: Various additions to the web account manager.
Track if we are accessed via HTTP or HTTPS, and the server name.
Track cookies.
HEAD method.
Various security clean ups.
Force HTTPS for account.html.
Poor mans Bobby Tables protection.
Security token.
Validate inputs. Looking up the DNS records for email domain name.
Don't allow creation of accounts with god names, leave that for the
console. Check if created user name exists already. Double check the
passwords and emails.
Error messages on dynamic pages.
Various clean ups.
TODO++
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 514 +++++++++++++++++-----
1 file changed, 406 insertions(+), 108 deletions(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index c18e09e..67cb8da 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -24,16 +24,34 @@ namespace OpenSim.Server.Handlers.Web
// This is all slow and clunky, it's not a real web server, just something to use if you don't want a real one.
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IConfigSource m_Config;
+
protected MySQLRaw m_database = null;
+
private Hashtable mime = new Hashtable();
private Hashtable ssi = new Hashtable();
+ private IPAddress m_IP;
+ private IHttpServer m_server;
+ private IHttpServer m_SSLserver = null;
+ private string m_domain = "";
+ private uint m_http_port;
+ private uint m_https_port = 0;
+
+ private Dictionary m_auth = new Dictionary();
+
+ private static Dictionary m_firstNames = new Dictionary();
+ private static Dictionary m_lastNames = new Dictionary();
+ private static Dictionary m_fullNames = new Dictionary();
+
public WebServerConnector(IConfigSource config, IHttpServer server, string configName) : base(config, server, configName)
{
string dllName = String.Empty;
string connString = String.Empty;
m_Config = config;
+ m_server = server;
+ m_IP = MainServer.Instance.ListenIPAddress;
+ m_http_port = server.Port;
// Try reading the [DatabaseService] section, if it exists
IConfig dbConfig = m_Config.Configs["DatabaseService"];
@@ -79,52 +97,104 @@ namespace OpenSim.Server.Handlers.Web
// mime.Add(".markdown","text/markdown");
mime.Add(".txt", "text/plain");
+ // Grab some info.
IConfig cfg = m_Config.Configs["GridInfoService"];
string HomeURI = Util.GetConfigVarFromSections(m_Config, "HomeURI", new string[] { "Startup", "Hypergrid" }, String.Empty);
ssi.Add("grid", cfg.GetString("gridname", "my grid"));
ssi.Add("uri", cfg.GetString("login", HomeURI));
ssi.Add("version", VersionInfo.Version);
+ cfg = m_Config.Configs["Const"];
+ m_domain = cfg.GetString("HostName", "localhost");
+ // Copied from OpenSim/Region/OptionalModules/ViewerSupport/GodNamesModule.cs
+ cfg = m_Config.Configs["GodNames"];
+ if (null != cfg)
+ {
+ m_log.Info("[WEB SERVICE]: Loading god names.");
+ string conf_str = cfg.GetString("FullNames", String.Empty);
+ if (String.Empty != conf_str)
+ {
+ foreach (string strl in conf_str.Split(','))
+ {
+ string strlan = strl.Trim(" \t".ToCharArray());
+ m_log.InfoFormat("[WEB SERVICE]: Adding {0} as a god name", strlan);
+ m_fullNames.Add(strlan, strlan);
+ }
+ }
+
+ conf_str = cfg.GetString("FirstNames", String.Empty);
+ if (String.Empty != conf_str)
+ {
+ foreach (string strl in conf_str.Split(','))
+ {
+ string strlan = strl.Trim(" \t".ToCharArray());
+ m_log.InfoFormat("[WEB SERVICE]: Adding {0} as a god first name", strlan);
+ m_firstNames.Add(strlan, strlan);
+ }
+ }
+
+ conf_str = cfg.GetString("Surnames", String.Empty);
+ if (String.Empty != conf_str)
+ {
+ foreach (string strl in conf_str.Split(','))
+ {
+ string strlan = strl.Trim(" \t".ToCharArray());
+ m_log.InfoFormat("[WEB SERVICE]: Adding {0} as a god last name", strlan);
+ m_lastNames.Add(strlan, strlan);
+ }
+ }
+ }
+ else
+ m_log.Info("[WEB SERVICE]: No god names loaded.");
+
+ // Add the HTTP and HTTPS handlers.
server.AddHTTPHandler("/web/", WebRequestHandler);
IConfig networkConfig = m_Config.Configs["Network"];
if (null != networkConfig)
{
- uint https_port = (uint) networkConfig.GetInt("https_port", 0);
- if (0 != https_port)
+ m_https_port = (uint) networkConfig.GetInt("https_port", 0);
+ if (0 != m_https_port)
{
- server = MainServer.GetHttpServer(https_port, null);
- if (null != server)
- server.AddHTTPHandler("/web/", WebRequestHandler);
+ m_SSLserver = MainServer.GetHttpServer(m_https_port, null);
+ if (null != m_SSLserver)
+ m_SSLserver.AddHTTPHandler("/web/", WebRequestHandlerSSL);
}
- }
- }
+ }
+ }
+ // AAARGGGH, in the request we don't get the HTTP/S, domain name, nor port number we were called from. So we have to fake it, sorta.
+ private Hashtable WebRequestHandlerSSL(Hashtable request)
+ {
+ return Handler(request, true);
+ }
private Hashtable WebRequestHandler(Hashtable request)
{
- long locIn = m_database.Count("Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
- long HGin = m_database.Count("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
- long locOut = m_database.Count("hg_traveling_data", "GridExternalName != '" + ssi["uri"] + "'"); // Locals that are HGing.
+ return Handler(request, false);
+ }
+ private Hashtable Handler(Hashtable request, bool usedSSL)
+ {
Hashtable reply = new Hashtable();
Hashtable replyHeaders = new Hashtable();
- ssi["members"] = m_database.Count("UserAccounts").ToString();
- ssi["sims"] = m_database.Count("regions").ToString();
- ssi["inworld"] = (locIn - HGin).ToString();
- ssi["outworld"] = locOut.ToString();
- ssi["hgers"] = HGin.ToString();
- ssi["month"] = m_database.Count("GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))").ToString();
+ Hashtable cookies = new Hashtable();
+ Hashtable fields = new Hashtable();
+ List errors = new List();
string reqpath = (string) request["uri"];
- string[] query = (string[]) request["querystringkeys"];
- Hashtable headers = (Hashtable) request["headers"];
string method = (string) request["http-method"];
string type = (string) request["content-type"];
string body = (string) request["body"];
+ string[] query = (string[]) request["querystringkeys"];
+ Hashtable headers = (Hashtable) request["headers"];
+ Hashtable vars = (Hashtable) request["requestvars"];
string file = reqpath.Remove(0, 5);
string path = Path.Combine(Util.webDir(), file);
+ m_log.InfoFormat("[WEB SERVICE]: {0} {1} {2} : {3} {4}, server IP {5} content type {6}, body {7}.",
+ headers["remote_addr"].ToString(), method, m_domain, (usedSSL ? m_https_port : m_http_port), reqpath, m_IP, type, body);
+
if (! Path.GetFullPath(path).StartsWith(Path.GetFullPath(Util.webDir())))
{
- m_log.ErrorFormat("[WEB SERVICE]: INVALID PATH {0} != {1}", Path.GetFullPath(path), Path.GetFullPath(Util.webDir()));
+ m_log.ErrorFormat("[WEB SERVICE]: INVALID PATH {0} != {1}", Path.GetFullPath(path), Path.GetFullPath(Util.webDir()));
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
reply["str_response_string"] = "404 Unknown page " +
@@ -132,13 +202,59 @@ namespace OpenSim.Server.Handlers.Web
return reply;
}
- m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} contont type {2} body {3}.", method, reqpath, type, body);
+ long locIn = m_database.Count("Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
+ long HGin = m_database.Count("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
+ ssi["hgers"] = HGin.ToString();
+ ssi["inworld"] = (locIn - HGin).ToString();
+ ssi["outworld"] = m_database.Count("hg_traveling_data", "GridExternalName != '" + ssi["uri"] + "'").ToString(); // Locals that are HGing.
+ ssi["members"] = m_database.Count("UserAccounts").ToString();
+ ssi["month"] = m_database.Count("GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))").ToString();
+ ssi["sims"] = m_database.Count("regions").ToString();
+
foreach (DictionaryEntry h in headers)
- m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
+ {
+ if ("cookie" == h.Key.ToString())
+ {
+ string[] cks = h.Value.ToString().Split(';');
+ foreach (String c in cks)
+ {
+ string[] ck = c.Split('=');
+ cookies[ck[0].Trim(' ')] = ck[1].Trim(' ');
+ }
+ }
+ }
+
+ if ("POST" == method)
+ {
+ string[] bdy = body.Split('&');
+ body = "";
+ foreach (String bd in bdy)
+ {
+ string[] b = bd.Split('=');
+ if (b.Length == 0)
+ continue;
+ string n = System.Web.HttpUtility.UrlDecode(b[0]);
+ string v = "";
+ if (b.Length > 1)
+ v = System.Web.HttpUtility.UrlDecode(b[1]);
+ fields[n] = bobbyTables(n, v);
+ body = body + "" + n + " = " + v + "
\n";
+ }
+ }
+
foreach (String q in query)
- m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} query {2} value {3}", method, reqpath, q, (string) request[q]);
+ {
+ m_log.InfoFormat("[WEB SERVICE]: {0} {1} query {2} = {3}", method, reqpath, q, (string) request[q]);
+ fields[q] = bobbyTables(q, (string) request[q]);
+ }
+ foreach (DictionaryEntry h in headers)
+ m_log.DebugFormat("[WEB SERVICE]: {0} {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
+ // I dunno what these vars are or where they come from, never actually seen them.
+ foreach (DictionaryEntry h in vars)
+ m_log.InfoFormat("[WEB SERVICE]: {0} {1} var {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
reply["int_response_code"] = 200;
+
if (("GET" == method) || ("HEAD" == method))
{
if (File.Exists(path))
@@ -157,19 +273,19 @@ namespace OpenSim.Server.Handlers.Web
if (0 >= DateTime.Compare(ifdt, dt))
{
reply["int_response_code"] = 304;
- m_log.InfoFormat("[WEB SERVICE]: If-Modified-Since is earliar or equal to Last-Modified, from {0}", path);
+ m_log.InfoFormat("[WEB SERVICE]: If-Modified-Since is earliar or equal to Last-Modified, from {0}", reqpath);
reply["headers"] = replyHeaders;
if ("HEAD" == method)
{
- reply["bin_response_data"] = null;
- reply["str_response_string"] = null;
+ reply.Remove("bin_response_data");
+ reply.Remove("str_response_string");
}
return reply;
}
}
catch (Exception)
{
- m_log.ErrorFormat("[WEB SERVICE]: Invalid If-Modified-Since header, ignoring it, from {0}", path);
+ m_log.WarnFormat("[WEB SERVICE]: Invalid If-Modified-Since header, ignoring it, from {0} - {1}", reqpath, ifdtr);
}
}
replyHeaders["Last-Modified"] = dt.ToString("R");
@@ -192,10 +308,21 @@ namespace OpenSim.Server.Handlers.Web
else
{
if ("account.html" == file)
- reply["str_response_string"] = loginPage(null, "");
+ {
+ if (usedSSL)
+ reply["str_response_string"] = loginPage(null, "");
+ else // Force HTTPS by redirecting.
+ {
+ reply["int_response_code"] = 200;
+ reply["content_type"] = "text/html";
+ reply["str_response_string"] = "404 Unknown page " +
+ " " +
+ "";
+ }
+ }
else
{
- m_log.ErrorFormat("[WEB SERVICE]: Unable to read {0}.", path);
+ m_log.ErrorFormat("[WEB SERVICE]: Unable to read {0}.", reqpath);
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
reply["str_response_string"] = "404 Unknown page " +
@@ -205,124 +332,295 @@ namespace OpenSim.Server.Handlers.Web
}
else if ("POST" == method)
{
- Hashtable fields = new Hashtable();
- string[] bdy = body.Split('&');
- body = "";
- foreach (String bd in bdy)
- {
- string[] b = bd.Split('=');
- if (b.Length == 0)
- continue;
- String n = System.Web.HttpUtility.UrlDecode(b[0]);
- String v = "";
- if (b.Length > 1)
- v = System.Web.HttpUtility.UrlDecode(b[1]);
- if ((0 != String.Compare("password", n)) && (0 != String.Compare("psswrd", n)))
- {
- // Poor mans Bobby Tables protection.
- v = v.Replace("'", "_");
- v = v.Replace("\"", "_");
- v = v.Replace(";", "_");
- v = v.Replace("(", "_");
- v = v.Replace(")", "_");
- }
- fields[n] = v;
- body = body + "" + n + " = " + v + "
\n";
- }
if ("account.html" == file)
{
string doit = fields["doit"].ToString();
+ string toke_n_munchie = "";
replyHeaders["Cache-Control"] = "no-cache";
- if ("logout" == doit)
- reply["str_response_string"] = loginPage(null, "Logged out.");
- else if (("create" == doit) || ("confirm" == doit))
+
+/* TODO -
+ Switch to using prepared SQL statements.
+
+ Actually authenticate them.
+ Deal with dictionary attacks by slowing down access on password failures etc.
+
+ Regenerate token on authentication.
+ Store users UUID.
+
+ Invalidate token on logout and password reset.
+ Logout when invalidating tokens.
+
+ Deal with validation and password reset emails, likely with the same code.
+*/
+
+ if ((null == cookies["toke_n_munchie"]) || (null == fields["toke_n_munchie"])
+ || ("" == cookies["toke_n_munchie"].ToString()) || ("" == fields["toke_n_munchie"].ToString()))
+ toke_n_munchie = newSession(doit, headers, ref fields, ref replyHeaders);
+ else if (cookies["toke_n_munchie"].ToString() != fields["toke_n_munchie"].ToString())
+ errors.Add("Invalid session.");
+ else
{
- Regex rgxName = new Regex("^[a-zA-Z0-9]+$");
- Regex rgxEmail = new Regex("^.+@.+\\..+$");
- string[] names = fields["name"].ToString().Split(' ');
- if ("" == fields["email"].ToString())
- reply["str_response_string"] = loginPage(fields, "Please supply an email address when creating an account.");
- else if (!rgxEmail.IsMatch(fields["email"].ToString()))
- reply["str_response_string"] = loginPage(fields, "Please supply a valid email address when creating an account.");
- else if (!Uri.IsWellFormedUriString("mailto:" + fields["email"].ToString(), System.UriKind.Absolute))
- reply["str_response_string"] = loginPage(fields, "Please supply a valid email address when creating an account.");
-// TODO - the other test to do here is actually lookup the domain name, looking for any sort of record.
- else if (2 != names.Length)
- reply["str_response_string"] = loginPage(fields, "Please supply a two word name when creating an account.");
- // SL docs say 31 characters each for first and last name. UserAccounts table is varchar(64) each. userinfo has varchar(50) for the combined name.
- // The userinfo table seems to be obsolete.
- // Singularity at least limits the total name to 64.
- // I can't find any limitations on characters allowed, but I only ever see letters and digits used. Case is stored, but not significant.
- // OpenSims "create user" console command doesn't sanitize it at all, even crashing on some names.
- else if (31 < names[0].Length)
- reply["str_response_string"] = loginPage(fields, "First and last names are limited to 31 letters each.");
- else if (31 < names[1].Length)
- reply["str_response_string"] = loginPage(fields, "First and last names are limited to 31 letters each.");
- else if (!rgxName.IsMatch(names[0]))
- reply["str_response_string"] = loginPage(fields, "First and last names are limited to letters and digits.");
- else if (!rgxName.IsMatch(names[1]))
- reply["str_response_string"] = loginPage(fields, "First and last names are limited to letters and digits.");
-// TODO - check and disallow god names, those are done in the console.
+ toke_n_munchie = cookies["toke_n_munchie"].ToString();
+ Hashtable auth = m_auth[toke_n_munchie];
+ if (null == auth)
+ {
+ errors.Add("Null session.");
+ m_log.InfoFormat("[WEB SERVICE]: Null session {0} - {1}.", toke_n_munchie, doit);
+ }
else
{
- if (("create" == doit))
- reply["str_response_string"] = accountCreationPage(body, fields);
+// TODO - maybe check if session has expired, but how long should they last?
+ if (auth["IP"].ToString() != headers["remote_addr"].ToString())
+ errors.Add("Wrong IP for session.");
else
{
- if (0 != String.Compare(fields["psswrd"].ToString(), fields["password"].ToString()))
- reply["str_response_string"] = loginPage(fields, "Passwords are not the same.");
- else
- reply["str_response_string"] = loggedOnPage(body, fields);
+ auth["time"] = DateTime.Now;
+ m_auth[toke_n_munchie] = auth;
+ m_log.InfoFormat("[WEB SERVICE]: New timestamp for session {0} - {1}.", toke_n_munchie, doit);
}
}
}
+
+ if (0 != errors.Count)
+ deleteSession(toke_n_munchie, doit, headers, ref fields, ref replyHeaders);
+
+ if (("https://" + m_domain + ":" + m_https_port.ToString() + "/web/account.html") != headers["referer"].ToString())
+ errors.Add("Invalid referer.");
+
+ validateName(false, fields, ref errors);
+
+ if ("logout" == doit)
+ {
+ deleteSession(toke_n_munchie, doit, headers, ref fields, ref replyHeaders);
+ errors.Add("Logged out.");
+ }
+ else if (("create" == doit) || ("confirm" == doit))
+ {
+ validateName(true, fields, ref errors);
+ validateEmail(fields, ref errors);
+ if ("confirm" == doit)
+ validatePassword(fields, ref errors);
+ if (0 == errors.Count)
+ {
+ // Check the account name doesn't exist yet.
+ // Which might be tricky, apparently names are not case sensitive on login, but stored with case in the database.
+ // I confirmed that, can log in no matter what case you use.
+ // UserAccounts FirstName and LastName fields are both varchar(64) utf8_general_ci.
+ // The MySQL docs say that the "_ci" bit means comparisons will be case insensitive. So that should work fine.
+ // No need for prepared SQL here, the names have already been checked.
+ string[] names = fields["name"].ToString().Split(' ');
+ long c = m_database.Count("UserAccounts", "FirstName = '" + names[0] + "' AND LastName = '" + names[1] + "'");
+ if (0 != c)
+ errors.Add("Pick a different name.");
+ else if (("create" == doit))
+ reply["str_response_string"] = accountCreationPage(fields, body);
+ else
+ reply["str_response_string"] = loggedOnPage(fields, body);
+ }
+ else
+ deleteSession(toke_n_munchie.ToString(), doit, headers, ref fields, ref replyHeaders);
+ }
else if ("cancel" == doit)
{
- reply["str_response_string"] = loginPage(null, "Cancelled.");
+ deleteSession(toke_n_munchie.ToString(), doit, headers, ref fields, ref replyHeaders);
+ errors.Add("Cancelled.");
}
else if ("list" == doit)
{
+// TODO - should check if the user is a god before allowing this.
List< Hashtable > rows = m_database.Select("UserAccounts",
- "CONCAT(FirstName,' ',LastName) as Name,UserTitle as Title,UserLevel as Level,UserFlags as Flags,PrincipalID as UUID",
+ "CONCAT(FirstName,' ',LastName) as Name,UserTitle as Title,UserLevel as Level,UserFlags as Flags,PrincipalID as UUID",
"", "Name");
reply["str_response_string"] = "member accounts " +
table(rows, new string[5] {"Name", "Title", "Level", "Flags", "UUID"}, "member accounts",
- "account.html?doit=edit&token=" + fields["token"].ToString(), "UUID") + "" + button("my account") + "
";
+ "account.html?doit=edit&token=" + fields["toke_n_munchie"].ToString(), "UUID") + "" + button("my account") + "
";
}
- else
+ else if ("login" == doit)
{
- reply["str_response_string"] = loggedOnPage(body, fields);
+ if (0 != errors.Count)
+ deleteSession(toke_n_munchie.ToString(), doit, headers, ref fields, ref replyHeaders);
+ else
+ reply["str_response_string"] = loggedOnPage(fields, body);
}
+ else
+ reply["str_response_string"] = loggedOnPage(fields, body);
}
- else
+ else // Not one of our dynamic pages.
{
- m_log.ErrorFormat("[WEB SERVICE]: No such POST target {0}.", path);
+ m_log.ErrorFormat("[WEB SERVICE]: No such POST target {0}.", path);
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
reply["str_response_string"] = "404 Unknown page " +
"404 error, can't find the " + reqpath + " page.
";
}
}
- else
+ else // Not one of our handled methods.
{
- m_log.ErrorFormat("[WEB SERVICE]: UNKNOWN method {0} path {1}.", method, reqpath);
+ m_log.ErrorFormat("[WEB SERVICE]: UNKNOWN method {0} path {1}.", method, reqpath);
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
reply["str_response_string"] = "Unknown method " +
"HUH! For " + reqpath + " page.
";
}
- m_log.Info("[WEB SERVICE]: ");
reply["headers"] = replyHeaders;
+ if (0 != errors.Count)
+ {
+ string b = "";
+ foreach (string e in errors)
+ b = b + "" + e + "
";
+ reply["str_response_string"] = loginPage(fields, b);
+ }
+
if ("HEAD" == method)
{
- reply["bin_response_data"] = null;
- reply["str_response_string"] = null;
+ reply.Remove("bin_response_data");
+ reply.Remove("str_response_string");
}
return reply;
}
+ // Poor mans Bobby Tables protection.
+ private string bobbyTables(string n, string v)
+ {
+ if ((0 != String.Compare("password", n)) && (0 != String.Compare("psswrd", n)))
+ {
+ v = v.Replace("'", "_");
+ v = v.Replace("\"", "_");
+ v = v.Replace(";", "_");
+ v = v.Replace("(", "_");
+ v = v.Replace(")", "_");
+ }
+ return v;
+ }
+
+ private string newSession(string doit, Hashtable headers, ref Hashtable fields, ref Hashtable replyHeaders)
+ {
+ // This is a little over the top, but apparently best practices.
+ string toke_n_munchie = Guid.NewGuid().ToString();
+ string salt = Util.Md5Hash(UUID.Random().ToString());
+ string hash = Util.Md5Hash(Util.Md5Hash(toke_n_munchie) + ":" + salt);
+ Hashtable auth = new Hashtable();
+ auth["toke_n_munchie"] = toke_n_munchie;
+ auth["salt"] = salt;
+ auth["hash"] = hash;
+ auth["IP"] = headers["remote_addr"].ToString();
+ auth["time"] = DateTime.Now;
+ auth["UUID"] = UUID.Zero;
+ m_auth[hash] = auth;
+ // For some odd reason, __Host- only works if "Path=/", and if no "Domain=", per the spec.
+ //replyHeaders["Set-Cookie"] = "__Host-toke_n_munchie=" + toke_n_munchie + "; HttpOnly; Path=/web/; SameSite=Strict; Secure;";
+ replyHeaders["Set-Cookie"] = "toke_n_munchie=" + hash + "; HttpOnly; Path=/web/; SameSite=Strict; Secure;";
+ fields["toke_n_munchie"] = hash;
+ toke_n_munchie = hash;
+ m_log.InfoFormat("[WEB SERVICE]: {0} New session {1} - {2}.", headers["remote_addr"].ToString(), toke_n_munchie, doit);
+ return toke_n_munchie;
+ }
+
+ private void deleteSession(string session, string doit, Hashtable headers, ref Hashtable fields, ref Hashtable replyHeaders)
+ {
+ m_log.InfoFormat("[WEB SERVICE]: {0} Deleted session {1} - {2}.", headers["remote_addr"].ToString(), session, doit);
+ m_auth.Remove(session);
+ fields.Remove("toke_n_munchie");
+ replyHeaders["Set-Cookie"] = "toke_n_munchie=\"\"; HttpOnly; Path=/web/; SameSite=Strict; Secure; expires=Thu, 01 Jan 1970 00:00:00 GMT;";
+ }
+
+ private void validateEmail(Hashtable fields, ref List errors)
+ {
+ Regex rgxEmail = new Regex("^.+@.+\\..+$");
+ if ((null == fields["email"]) || ("" == fields["email"].ToString()))
+ errors.Add("Please supply an email address.");
+ else if (!rgxEmail.IsMatch(fields["email"].ToString()))
+ errors.Add("Please supply a proper email address.");
+ else if (!Uri.IsWellFormedUriString("mailto:" + fields["email"].ToString(), System.UriKind.Absolute))
+ errors.Add("Please supply a valid email address.");
+
+ // Actually lookup the domain name, looking for any sort of record.
+ string e = fields["email"].ToString().Split('@')[1];
+ IPHostEntry ip = null;
+ try
+ {
+ ip = Dns.GetHostEntry(e);
+ }
+ catch(Exception)
+ {
+ }
+ if (null == ip)
+ errors.Add("Can't find that email server, try a different email address.");
+ }
+
+ private void validateName(bool godCheck, Hashtable fields, ref List errors)
+ {
+ Regex rgxName = new Regex("^[a-zA-Z0-9]+$");
+ string[] names;
+ if ((null == fields["name"]) || ("" == fields["name"].ToString()))
+ errors.Add("Please supply an account name.");
+ else
+ {
+ names = fields["name"].ToString().Split(' ');
+ if (2 != names.Length)
+ errors.Add("Names have to be two words.");
+ // SL docs say 31 characters each for first and last name. UserAccounts table is varchar(64) each. userinfo has varchar(50) for the combined name.
+ // The userinfo table seems to be obsolete.
+ // Singularity at least limits the total name to 64.
+ // I can't find any limitations on characters allowed, but I only ever see letters and digits used. Case is stored, but not significant.
+ // OpenSims "create user" console command doesn't sanitize it at all, even crashing on some names.
+ else
+ {
+ if ((31 < names[0].Length) || (31 < names[1].Length))
+ errors.Add("First and last names are limited to 31 letters each.");
+ if ((!rgxName.IsMatch(names[0])) || (!rgxName.IsMatch(names[1])))
+ errors.Add("First and last names are limited to letters and digits.");
+ if (godCheck)
+ {
+ // Check and disallow god names, those are done in the console.
+ bool f = false;
+ try
+ {
+ if (null != m_fullNames[names[0] + " " + names[1]])
+ {
+ f = true;
+ errors.Add("Pick another name.");
+ }
+ }
+ catch (Exception)
+ {
+ }
+ if (!f)
+ {
+ try
+ {
+ if (null != m_firstNames[names[0]])
+ errors.Add("Pick another first name.");
+ }
+ catch (Exception)
+ {
+ }
+ try
+ {
+ if (null != m_firstNames[names[1]])
+ errors.Add("Pick another last name.");
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void validatePassword(Hashtable fields, ref List errors)
+ {
+ if ((null == fields["password"]) || ("" == fields["password"].ToString()))
+ errors.Add("Please supply a password.");
+ else if ((null == fields["psswrd"]) || ("" == fields["psswrd"].ToString()))
+ errors.Add("Please supply a password.");
+ else if (0 != String.Compare(fields["psswrd"].ToString(), fields["password"].ToString()))
+ errors.Add("Passwords are not the same.");
+ }
+
private string loginPage(Hashtable fields, string message)
{
string n = "";
@@ -345,29 +643,29 @@ namespace OpenSim.Server.Handlers.Web
+ footer();
}
- private string accountCreationPage(string body, Hashtable fields)
+ private string accountCreationPage(Hashtable fields, string message)
{
return header(ssi["grid"] + " account")
+ "Creating " + ssi["grid"] + " account for " + fields["name"].ToString() + " "
- + form("account.html", fields["token"].ToString(),
+ + form("account.html", fields["toke_n_munchie"].ToString(),
hidden("name", fields["name"].ToString())
+ hidden("psswrd", fields["password"].ToString())
- + text("email", "An email will be sent to", "email", fields["email"].ToString(), 254, true)
+ + text("email", "An email will be sent to", "email", fields["email"].ToString(), 254, false)
+ " to validate it, please double check this."
- + text("password", "Re-enter your password", "password", "", 0, true)
+ + text("password", "Re-enter your password", "password", "", 0, false)
+ "Warning, the limit on password length is set by your viewer, some can't handle longer than 16 characters."
+ button("confirm")
+ button("cancel")
)
- + body
+ + message
+ footer();
}
- private string loggedOnPage(string body, Hashtable fields)
+ private string loggedOnPage(Hashtable fields, string message)
{
return header(ssi["grid"] + " account")
+ "" + ssi["grid"] + " account for " + fields["name"].ToString() + " "
- + form("account.html", fields["token"].ToString(),
+ + form("account.html", fields["toke_n_munchie"].ToString(),
hidden("name", fields["name"].ToString())
// + hidden("UUID", fields["UUID"].ToString())
+ text("email", "email", "email", fields["email"].ToString(), 254, true)
@@ -386,7 +684,7 @@ namespace OpenSim.Server.Handlers.Web
// + button("read")
+ button("update")
)
- + body
+ + message
+ footer();
}
@@ -444,7 +742,7 @@ namespace OpenSim.Server.Handlers.Web
private string form(string action, string token, string form)
{
- return " \n";
+ return " \n";
}
private string hidden(string name, string val)
--
cgit v1.1
From 24d6b61d9c99e3119b0a765ffe6ef5c10e05d563 Mon Sep 17 00:00:00 2001
From: onefang
Date: Sun, 18 Aug 2019 15:45:03 +1000
Subject: TODO++
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 3 +++
1 file changed, 3 insertions(+)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index 67cb8da..cbfee3a 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -352,6 +352,9 @@ namespace OpenSim.Server.Handlers.Web
Logout when invalidating tokens.
Deal with validation and password reset emails, likely with the same code.
+
+ Deal with editing yourself.
+ Deal with editing others, but only as god.
*/
if ((null == cookies["toke_n_munchie"]) || (null == fields["toke_n_munchie"])
--
cgit v1.1
From 82d9bf5258a9627eac4c012f3d6f3e40036d0dee Mon Sep 17 00:00:00 2001
From: onefang
Date: Sun, 18 Aug 2019 15:45:43 +1000
Subject: Optimise account name validation.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index cbfee3a..c4a0531 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -391,7 +391,8 @@ namespace OpenSim.Server.Handlers.Web
if (("https://" + m_domain + ":" + m_https_port.ToString() + "/web/account.html") != headers["referer"].ToString())
errors.Add("Invalid referer.");
- validateName(false, fields, ref errors);
+ // Include a check for god names if we are creating a new account.
+ string[] names = validateName(("create" == doit) || ("confirm" == doit), fields, ref errors);
if ("logout" == doit)
{
@@ -400,7 +401,6 @@ namespace OpenSim.Server.Handlers.Web
}
else if (("create" == doit) || ("confirm" == doit))
{
- validateName(true, fields, ref errors);
validateEmail(fields, ref errors);
if ("confirm" == doit)
validatePassword(fields, ref errors);
@@ -412,9 +412,7 @@ namespace OpenSim.Server.Handlers.Web
// UserAccounts FirstName and LastName fields are both varchar(64) utf8_general_ci.
// The MySQL docs say that the "_ci" bit means comparisons will be case insensitive. So that should work fine.
// No need for prepared SQL here, the names have already been checked.
- string[] names = fields["name"].ToString().Split(' ');
- long c = m_database.Count("UserAccounts", "FirstName = '" + names[0] + "' AND LastName = '" + names[1] + "'");
- if (0 != c)
+ if (0 != m_database.Count("UserAccounts", "FirstName = '" + names[0] + "' AND LastName = '" + names[1] + "'"))
errors.Add("Pick a different name.");
else if (("create" == doit))
reply["str_response_string"] = accountCreationPage(fields, body);
@@ -553,10 +551,10 @@ namespace OpenSim.Server.Handlers.Web
errors.Add("Can't find that email server, try a different email address.");
}
- private void validateName(bool godCheck, Hashtable fields, ref List errors)
+ private string[] validateName(bool godCheck, Hashtable fields, ref List errors)
{
Regex rgxName = new Regex("^[a-zA-Z0-9]+$");
- string[] names;
+ string[] names = {"", ""};
if ((null == fields["name"]) || ("" == fields["name"].ToString()))
errors.Add("Please supply an account name.");
else
@@ -612,6 +610,7 @@ namespace OpenSim.Server.Handlers.Web
}
}
}
+ return names;
}
private void validatePassword(Hashtable fields, ref List errors)
--
cgit v1.1
From 14616c8438fef749c5e7cb6bdcefe9b7c9554597 Mon Sep 17 00:00:00 2001
From: onefang
Date: Wed, 21 Aug 2019 21:50:16 +1000
Subject: Disable the account manager for now, and anything else HTTPS related.
Bump it to the next version.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 92 ++++++++++++++++++-----
1 file changed, 74 insertions(+), 18 deletions(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index c4a0531..0f328d9 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
+using System.Net.Mail;
using System.Reflection;
using System.Security;
using System.Text;
@@ -32,10 +33,10 @@ namespace OpenSim.Server.Handlers.Web
private IPAddress m_IP;
private IHttpServer m_server;
- private IHttpServer m_SSLserver = null;
+// private IHttpServer m_SSLserver = null;
private string m_domain = "";
private uint m_http_port;
- private uint m_https_port = 0;
+// private uint m_https_port = 0;
private Dictionary m_auth = new Dictionary();
@@ -43,6 +44,15 @@ namespace OpenSim.Server.Handlers.Web
private static Dictionary m_lastNames = new Dictionary();
private static Dictionary m_fullNames = new Dictionary();
+/* TODO - shelved for now, rewrite it in Lua for lighttpd after the release.
+ private string m_SMTP_server;
+ private string m_SMTP_port;
+ private string m_SMTP_user;
+ private string m_SMTP_password;
+ private string m_email_from;
+ private SmtpClient m_smtp;
+*/
+
public WebServerConnector(IConfigSource config, IHttpServer server, string configName) : base(config, server, configName)
{
string dllName = String.Empty;
@@ -106,6 +116,7 @@ namespace OpenSim.Server.Handlers.Web
cfg = m_Config.Configs["Const"];
m_domain = cfg.GetString("HostName", "localhost");
+/* TODO - shelved for now, rewrite it in Lua for lighttpd after the release.
// Copied from OpenSim/Region/OptionalModules/ViewerSupport/GodNamesModule.cs
cfg = m_Config.Configs["GodNames"];
if (null != cfg)
@@ -147,8 +158,32 @@ namespace OpenSim.Server.Handlers.Web
else
m_log.Info("[WEB SERVICE]: No god names loaded.");
+ // Add the email client.
+ cfg = m_Config.Configs["SMTP"];
+ if (null != cfg)
+ {
+ m_log.Info("[WEB SERVICE]: Loading email configuration.");
+ m_SMTP_server = cfg.GetString("SMTP_SERVER_HOSTNAME", "127.0.0.1");
+ m_SMTP_port = cfg.GetString("SMTP_SERVER_PORT", "25");
+ m_SMTP_user = cfg.GetString("SMTP_SERVER_LOGIN", "");
+ m_SMTP_password = cfg.GetString("SMTP_SERVER_PASSWORD", "");
+ m_email_from = cfg.GetString("host_domain_header_from", "grid@localhost");
+
+ m_smtp = new SmtpClient
+ {
+ Host = m_SMTP_server,
+ Port = Convert.ToInt16(m_SMTP_port),
+ EnableSsl = false,
+ DeliveryMethod = SmtpDeliveryMethod.Network,
+ Credentials = new NetworkCredential(m_SMTP_user, m_SMTP_password),
+ Timeout = 20000
+ };
+ }
+*/
+
// Add the HTTP and HTTPS handlers.
server.AddHTTPHandler("/web/", WebRequestHandler);
+/* TODO - shelved for now, rewrite it in Lua for lighttpd after the release.
IConfig networkConfig = m_Config.Configs["Network"];
if (null != networkConfig)
{
@@ -160,13 +195,16 @@ namespace OpenSim.Server.Handlers.Web
m_SSLserver.AddHTTPHandler("/web/", WebRequestHandlerSSL);
}
}
+*/
}
// AAARGGGH, in the request we don't get the HTTP/S, domain name, nor port number we were called from. So we have to fake it, sorta.
+/* TODO - shelved for now, rewrite it in Lua for lighttpd after the release.
private Hashtable WebRequestHandlerSSL(Hashtable request)
{
return Handler(request, true);
}
+*/
private Hashtable WebRequestHandler(Hashtable request)
{
return Handler(request, false);
@@ -189,8 +227,10 @@ namespace OpenSim.Server.Handlers.Web
string file = reqpath.Remove(0, 5);
string path = Path.Combine(Util.webDir(), file);
+// m_log.InfoFormat("[WEB SERVICE]: {0} {1} {2} : {3} {4}, server IP {5} content type {6}, body {7}.",
+// headers["remote_addr"].ToString(), method, m_domain, (usedSSL ? m_https_port : m_http_port), reqpath, m_IP, type, body);
m_log.InfoFormat("[WEB SERVICE]: {0} {1} {2} : {3} {4}, server IP {5} content type {6}, body {7}.",
- headers["remote_addr"].ToString(), method, m_domain, (usedSSL ? m_https_port : m_http_port), reqpath, m_IP, type, body);
+ headers["remote_addr"].ToString(), method, m_domain, m_http_port, reqpath, m_IP, type, body);
if (! Path.GetFullPath(path).StartsWith(Path.GetFullPath(Util.webDir())))
{
@@ -244,14 +284,15 @@ namespace OpenSim.Server.Handlers.Web
foreach (String q in query)
{
- m_log.InfoFormat("[WEB SERVICE]: {0} {1} query {2} = {3}", method, reqpath, q, (string) request[q]);
+// m_log.InfoFormat("[WEB SERVICE]: {0} {1} query {2} = {3}", method, reqpath, q, (string) request[q]);
fields[q] = bobbyTables(q, (string) request[q]);
}
- foreach (DictionaryEntry h in headers)
- m_log.DebugFormat("[WEB SERVICE]: {0} {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
+// foreach (DictionaryEntry h in headers)
+// m_log.DebugFormat("[WEB SERVICE]: {0} {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
// I dunno what these vars are or where they come from, never actually seen them.
- foreach (DictionaryEntry h in vars)
- m_log.InfoFormat("[WEB SERVICE]: {0} {1} var {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
+ // Ah, viewers send them, and they seem to be identical to the query that viewers also send.
+// foreach (DictionaryEntry h in vars)
+// m_log.InfoFormat("[WEB SERVICE]: {0} {1} var {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value);
reply["int_response_code"] = 200;
@@ -305,6 +346,7 @@ namespace OpenSim.Server.Handlers.Web
csr.Close();
}
}
+/* TODO - shelved for now, rewrite it in Lua for lighttpd after the release.
else
{
if ("account.html" == file)
@@ -329,15 +371,16 @@ namespace OpenSim.Server.Handlers.Web
"404 error, can't find the " + reqpath + " page.
";
}
}
+*/
}
else if ("POST" == method)
{
- if ("account.html" == file)
- {
- string doit = fields["doit"].ToString();
- string toke_n_munchie = "";
- replyHeaders["Cache-Control"] = "no-cache";
+// if ("account.html" == file)
+// {
+// string doit = fields["doit"].ToString();
+// string toke_n_munchie = "";
+// replyHeaders["Cache-Control"] = "no-cache";
/* TODO -
Switch to using prepared SQL statements.
@@ -356,7 +399,7 @@ namespace OpenSim.Server.Handlers.Web
Deal with editing yourself.
Deal with editing others, but only as god.
*/
-
+/* TODO - shelved for now, rewrite it in Lua for lighttpd after the release.
if ((null == cookies["toke_n_munchie"]) || (null == fields["toke_n_munchie"])
|| ("" == cookies["toke_n_munchie"].ToString()) || ("" == fields["toke_n_munchie"].ToString()))
toke_n_munchie = newSession(doit, headers, ref fields, ref replyHeaders);
@@ -417,7 +460,19 @@ namespace OpenSim.Server.Handlers.Web
else if (("create" == doit))
reply["str_response_string"] = accountCreationPage(fields, body);
else
+ {
+ var fromAddress = new MailAddress(m_email_from, (string) ssi["grid"]);
+ var toAddress = new MailAddress((string) fields["email"], (string) fields["name"]);
+ using (var message = new MailMessage(fromAddress, toAddress)
+ {
+ Subject = "validation email",
+ Body = "Should be a linky around here somewhere."
+ })
+ {
+ m_smtp.Send(message);
+ }
reply["str_response_string"] = loggedOnPage(fields, body);
+ }
}
else
deleteSession(toke_n_munchie.ToString(), doit, headers, ref fields, ref replyHeaders);
@@ -446,15 +501,16 @@ namespace OpenSim.Server.Handlers.Web
}
else
reply["str_response_string"] = loggedOnPage(fields, body);
- }
- else // Not one of our dynamic pages.
- {
+*/
+// }
+// else // Not one of our dynamic pages.
+// {
m_log.ErrorFormat("[WEB SERVICE]: No such POST target {0}.", path);
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
reply["str_response_string"] = "404 Unknown page " +
"404 error, can't find the " + reqpath + " page.
";
- }
+// }
}
else // Not one of our handled methods.
{
--
cgit v1.1
From 2c4a60c5d686168e54d71b0582b8d78eafec00a1 Mon Sep 17 00:00:00 2001
From: onefang
Date: Thu, 22 Aug 2019 15:47:24 +1000
Subject: More stats, and an actual stats web page.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 37 +++++++++++++++++++++--
1 file changed, 34 insertions(+), 3 deletions(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index 0f328d9..f06d4d8 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -242,14 +242,45 @@ namespace OpenSim.Server.Handlers.Web
return reply;
}
- long locIn = m_database.Count("Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
- long HGin = m_database.Count("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
+ long locIn = m_database.Count("Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world.
+ long HGin = m_database.Count("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world.
ssi["hgers"] = HGin.ToString();
ssi["inworld"] = (locIn - HGin).ToString();
ssi["outworld"] = m_database.Count("hg_traveling_data", "GridExternalName != '" + ssi["uri"] + "'").ToString(); // Locals that are HGing.
ssi["members"] = m_database.Count("UserAccounts").ToString();
- ssi["month"] = m_database.Count("GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))").ToString();
ssi["sims"] = m_database.Count("regions").ToString();
+ ssi["onlineSims"] = m_database.Count("regions", "sizeX != 0").ToString();
+ ssi["varRegions"] = m_database.Count("regions", "sizeX > 256 or sizeY > 256").ToString();
+ ssi["singleSims"] = m_database.Count("regions", "sizeX = 256 and sizeY = 256").ToString();
+ ssi["offlineSims"] = m_database.Count("regions", "sizeX = 0").ToString();
+
+ // Calculate grid area.
+ long simSize = 0;
+ List< Hashtable > rows = m_database.Select("regions", "sizeX,sizeY", "sizeX != 0", "");
+ foreach (Hashtable row in rows)
+ {
+ simSize = simSize + Convert.ToInt32(row["sizeX"]) * Convert.ToInt32(row["sizeY"]);
+ }
+ ssi["simsSize"] = simSize.ToString();
+
+ // Count local and HG visitors for the last 30 and 60 days.
+ HGin = m_database.Count("GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))");
+ rows = m_database.Join("GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
+ "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))", "");
+ locIn = rows.Count;
+ ssi["locDay30"] = locIn.ToString();
+ ssi["day30"] = HGin.ToString();
+ HGin = HGin - locIn;
+ ssi["HGday30"] = HGin.ToString();
+
+ HGin = m_database.Count("GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))");
+ rows = m_database.Join("GridUser", "GridUser.UserID", "INNER JOIN UserAccounts ON GridUser.UserID = UserAccounts.PrincipalID",
+ "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 4838400))", "");
+ locIn = rows.Count;
+ ssi["locDay60"] = locIn.ToString();
+ ssi["day60"] = HGin.ToString();
+ HGin = HGin - locIn;
+ ssi["HGday60"] = HGin.ToString();
foreach (DictionaryEntry h in headers)
{
--
cgit v1.1
From 817dabf5cb07044c2fee331455a0bb961b13dd46 Mon Sep 17 00:00:00 2001
From: onefang
Date: Sun, 25 Aug 2019 14:15:28 +1000
Subject: Now with favicons.
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index f06d4d8..11bf435 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -237,7 +237,7 @@ namespace OpenSim.Server.Handlers.Web
m_log.ErrorFormat("[WEB SERVICE]: INVALID PATH {0} != {1}", Path.GetFullPath(path), Path.GetFullPath(Util.webDir()));
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
- reply["str_response_string"] = "404 Unknown page " +
+ reply["str_response_string"] = "404 Unknown page " +
"404 error, can't find the " + reqpath + " page.
";
return reply;
}
@@ -398,7 +398,7 @@ namespace OpenSim.Server.Handlers.Web
m_log.ErrorFormat("[WEB SERVICE]: Unable to read {0}.", reqpath);
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
- reply["str_response_string"] = "404 Unknown page " +
+ reply["str_response_string"] = "404 Unknown page " +
"404 error, can't find the " + reqpath + " page.
";
}
}
@@ -519,7 +519,7 @@ namespace OpenSim.Server.Handlers.Web
List< Hashtable > rows = m_database.Select("UserAccounts",
"CONCAT(FirstName,' ',LastName) as Name,UserTitle as Title,UserLevel as Level,UserFlags as Flags,PrincipalID as UUID",
"", "Name");
- reply["str_response_string"] = "member accounts " +
+ reply["str_response_string"] = "member accounts " +
table(rows, new string[5] {"Name", "Title", "Level", "Flags", "UUID"}, "member accounts",
"account.html?doit=edit&token=" + fields["toke_n_munchie"].ToString(), "UUID") + "" + button("my account") + "
";
}
@@ -539,7 +539,7 @@ namespace OpenSim.Server.Handlers.Web
m_log.ErrorFormat("[WEB SERVICE]: No such POST target {0}.", path);
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
- reply["str_response_string"] = "404 Unknown page " +
+ reply["str_response_string"] = "404 Unknown page " +
"404 error, can't find the " + reqpath + " page.
";
// }
}
@@ -548,7 +548,7 @@ namespace OpenSim.Server.Handlers.Web
m_log.ErrorFormat("[WEB SERVICE]: UNKNOWN method {0} path {1}.", method, reqpath);
reply["int_response_code"] = 404;
reply["content_type"] = "text/html";
- reply["str_response_string"] = "Unknown method " +
+ reply["str_response_string"] = "Unknown method " +
"HUH! For " + reqpath + " page.
";
}
--
cgit v1.1
From 9d6c4374e6e2836fdb5d8703e4e92549c9d96b22 Mon Sep 17 00:00:00 2001
From: onefang
Date: Sat, 31 Aug 2019 14:28:57 +1000
Subject: typo--
---
OpenSim/Server/Handlers/Web/WebServerConnector.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'OpenSim/Server')
diff --git a/OpenSim/Server/Handlers/Web/WebServerConnector.cs b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
index 11bf435..5389c6e 100644
--- a/OpenSim/Server/Handlers/Web/WebServerConnector.cs
+++ b/OpenSim/Server/Handlers/Web/WebServerConnector.cs
@@ -345,7 +345,7 @@ namespace OpenSim.Server.Handlers.Web
if (0 >= DateTime.Compare(ifdt, dt))
{
reply["int_response_code"] = 304;
- m_log.InfoFormat("[WEB SERVICE]: If-Modified-Since is earliar or equal to Last-Modified, from {0}", reqpath);
+ m_log.InfoFormat("[WEB SERVICE]: If-Modified-Since is earlier or equal to Last-Modified, from {0}", reqpath);
reply["headers"] = replyHeaders;
if ("HEAD" == method)
{
--
cgit v1.1