Esempio n. 1
0
 /// <summary>Parses some arguments and acts upon them</summary>
 /// <param name="args">Array of strings, directly from the command line</param>
 /// <remarks>We have no arguments for now, this is mostly a placeholder (gwyneth 20220425)</remarks>
 private static void ParseArguments(string[] args)
 {
     // Very, very basic and naïve args passing.
     // There is probably a library to deal with this (gwyneth 20220425)
     if (args.Count() == 0)
     {
         DebugUtilities.WriteDebug("Good, no command-line arguments to parse.");
     }
     else if (args.Count() == 1)
     {
         if (args[0] == "--help")
         {
             DebugUtilities.WriteSpecial("Usage: RESTbot --config /path/to/configfile|--debug|--help");
             Environment.Exit(10);
         }
         else if (args[0] == "--debug")
         {
             DebugUtilities.WriteSpecial("`--debug` doesn't work yet...");
         }
     }
     else if (args.Count() == 2)
     {
         if (args[0] == "--config")
         {
             configFile = args[1];                       // should sanitise first (gwyneth 20220425)
             DebugUtilities.WriteDebug($"Command-line argument set configuration file to '{configFile}'");
         }
     }
     else
     {
         DebugUtilities.WriteSpecial("Usage: RESTbot --config /path/to/configfile|--debug|--help");
         Environment.Exit(10);
     }
 }         // end ParseArguments
Esempio n. 2
0
 public override void Think()
 {
     if (Active && me != null && target != null)
     {
         float distance = 0.0f;
         goalPos = target.Position;
         me
         .Client
         .Self
         .AutoPilot(goalPos.X + regionX, goalPos.Y + regionY, goalPos.Z);
         distance = Vector3.Distance(goalPos, me.Client.Self.SimPosition);
         if (distance > 30)
         {
             me.Client.Self.Movement.AlwaysRun = true;
         }
         else
         {
             me.Client.Self.Movement.AlwaysRun = false;
         }
         DebugUtilities.WriteDebug($"My pos = {me.Client.Self.SimPosition} Goal: {goalPos} and distance is: {distance}");
         if (distance < DISTANCE_BUFFER)
         {
             DebugUtilities.WriteDebug($"I am close to my goal pos: <{goalPos.X}, {goalPos.Y}, {goalPos.Z}");
             me.Client.Self.AutoPilotCancel();
             DebugUtilities.WriteSpecial("Cancel Autopilot");
             me.Client.Self.Movement.TurnToward(goalPos, true);
             Active = false;
         }
     }
     base.Think();
 }
Esempio n. 3
0
        public override void Think()
        {
            if (Active && me != null && attempts > 0)
            {
                float distance = 0.0f;
                distance = Vector3.Distance(goalPos, me.Client.Self.SimPosition);

                DebugUtilities
                .WriteDebug($"My pos = {me.Client.Self.SimPosition} Goal: {goalPos} and distance is: {distance}  (attempts left: {attempts})");
                if (distance < DISTANCE_BUFFER)
                {
                    DebugUtilities
                    .WriteDebug($"I am close to my goal pos: {goalPos.X}, {goalPos.Y}, {goalPos.Z}");
                    me.Client.Self.AutoPilotCancel();
                    DebugUtilities.WriteSpecial("Cancel Autopilot");
                    me.Client.Self.Movement.TurnToward(goalPos, true);
                    Active   = false;
                    attempts = 0;
                }
                else
                {
                    attempts--;
                }
            }
            else if (attempts <= 0)
            {
                DebugUtilities.WriteDebug("{MethodName}: No more attempts left; aborting...");
                Active = false;
            }
            base.Think();
        }
Esempio n. 4
0
        /// <summary>
        /// Process a request (assuming it exists)
        /// </summary>
        /// <param name="headers">Request headers (including path, etc.)</param>
        /// <param name="body">Request body (will usually have all parameters from POST)</param>
        public static string DoProcessing(RequestHeaders headers, string body)
        {
            // Abort if we don't even have a valid configuration; too many things depend on it... (gwyneth 20220213)
            if (Program.config == null)
            {
                return("<error>No valid configuration loaded, aborting</error>");
            }

            //Setup variables
            DebugUtilities.WriteInfo($"New request - {headers.RequestLine.Path}");
            //Split the URL
            string[] parts = headers.RequestLine.Path.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length < 1)
            {
                return("<error>invalidmethod</error>");
            }
            string Method = parts[0];
            /// <summary>Process the request params from POST, URL</summary>
            Dictionary <string, string> Parameters = RestBot.HandleDataFromRequest(headers, body);
            string debugparams = String.Empty;
            string debugparts  = String.Empty;

            foreach (KeyValuePair <string, string> kvp in Parameters)
            {
                debugparams = debugparams + "[" + kvp.Key + "=" + kvp.Value + "] ";
            }
            DebugUtilities.WriteDebug($"Parameters (total: {Parameters.Count()}) - '{debugparams}'");
            foreach (string s in parts)
            {
                debugparts = debugparts + "[ " + s + " ]";
            }
            DebugUtilities.WriteDebug($"Parts (total: {parts.Count()}) - '{debugparts}'");
            if (Method == "establish_session")
            {
                DebugUtilities.WriteDebug("We have an `establish_session` method.");
                // Alright, we're going to try to establish a session
                // Start location is optional (gwyneth 20220421)
                if (parts.Length >= 2 && parts[1] == Program.config.security.serverPass &&
                    Parameters.ContainsKey("first") && Parameters.ContainsKey("last") && Parameters.ContainsKey("pass"))
                {
                    DebugUtilities.WriteDebug("Found required parameters for establish_session");
                    if (Sessions != null)
                    {
                        foreach (KeyValuePair <UUID, Session> ss in Sessions)
                        {
                            if (ss.Value != null && ss.Value.Bot != null)
                            {
                                DebugUtilities.WriteSpecial($"Avatar check: [{ss.Value.Bot.First.ToLower()}/{ss.Value.Bot.Last.ToLower()}] = [{Parameters["first"].ToLower()}/{Parameters["last"].ToLower()}]");
                                if (Parameters["first"].ToLower() == ss.Value.Bot.First.ToLower() &&
                                    Parameters["last"].ToLower() == ss.Value.Bot.Last.ToLower()
                                    )
                                {
                                    DebugUtilities.WriteWarning($"Already running avatar {Parameters["first"]} {Parameters["last"]}");

                                    /// <value>Temporary string to construct a full response, if possible; if not, we catch the
                                    /// exception and return a much shorter version</value>
                                    /// <remarks>This is a hack. The issue is that we're probably acessing nullable
                                    /// elements without checking. (gwyneth 20220428)</remarks>
                                    string returnString = "";

                                    try
                                    {
                                        // Attempt to get a
                                        returnString = $@"<existing_session>true</existing_session>
	<session_id>{ss.Key.ToString()}</session_id>
	<key>{ss.Value.Bot.Client.Self.AgentID.ToString()}</key>
	<name>{ss.Value.Bot.Client.Self.FirstName} {ss.Value.Bot.Client.Self.LastName}</name>
	<FirstName>{ss.Value.Bot.Client.Self.FirstName}</FirstName>
	<LastName>{ss.Value.Bot.Client.Self.LastName}</LastName>
	<status>{ss.Value.Bot.myStatus.ToString()}</status>
	<uptime>{ss.Value.Bot.getUptimeISO8601()}</uptime>
	<start>{ss.Value.Bot.Start}</start>
	<CurrentSim>{ss.Value.Bot.Client.Network.CurrentSim.ToString()}</CurrentSim>
	<Position>{ss.Value.Bot.Client.Self.SimPosition.X},{ss.Value.Bot.Client.Self.SimPosition.Y},{ss.Value.Bot.Client.Self.SimPosition.Z}</Position>
	<Rotation>{ss.Value.Bot.Client.Self.SimRotation.X},{ss.Value.Bot.Client.Self.SimRotation.Y},{ss.Value.Bot.Client.Self.SimRotation.Z},{ss.Value.Bot.Client.Self.SimRotation.W}</Rotation>
";
                                    }
                                    catch (Exception e)
                                    {
                                        DebugUtilities.WriteError($"Could not generate full response, error was: '{e.Message}'; falling back to the simple, minimalistic answer");
                                        returnString = $"<existing_session>true</existing_session><session_id>{ss.Key.ToString()}</session_id>";
                                    }
                                    return(returnString);
                                }
                            }
                        }
                    }
                    else
                    {
                        DebugUtilities.WriteDebug("No available sessions...");
                    }
                    UUID    id = UUID.Random();
                    Session s  = new Session();
                    s.ID           = id;
                    s.Hostname     = headers.Hostname;
                    s.LastAccessed = DateTime.Now;
                    // Needs the $1$ for the md5 on the login for LibreMetaverse
                    if (!Parameters["pass"].StartsWith("$1$"))
                    {
                        Parameters["pass"] = "******" + Parameters["pass"];
                    }
                    // check if user has provided us with a starting location (default is to use the last location)
                    // (gwyneth 20220420)
                    string gridLocation = Parameters.ContainsKey("start") ? Parameters["start"] : "last";
                    s.Bot = new RestBot(s.ID, Parameters["first"], Parameters["last"], Parameters["pass"], gridLocation);

                    if (Sessions != null)
                    {
                        lock (Sessions)
                        {
                            Sessions.Add(id, s);
                        }
                    }
                    else
                    {
                        // no "else", we have no dictionary
                        DebugUtilities.WriteWarning("Possible issue: we have null Sessions when adding, which shouldn't happen");
                    }
                    RestBot.LoginReply reply = s.Bot.Login();
                    if (reply.wasFatal)
                    {
                        if (Sessions != null)
                        {
                            lock (Sessions)
                            {
                                if (Sessions.ContainsKey(id))
                                {
                                    Sessions.Remove(id);
                                }
                            }
                        }
                        else
                        {
                            // no "else", we have no dictionary
                            DebugUtilities.WriteWarning("Possible issue: we have null Sessions when removing, which shouldn't happen");
                        }
                    }
                    return(reply.xmlReply);
                }
                else
                {
                    String result = String.Empty;
                    if (parts.Length < 2)
                    {
                        result = "Missing a part.";
                    }
                    if (!Parameters.ContainsKey("first"))
                    {
                        result = result + " Missing 'first' arg.";
                    }
                    if (!Parameters.ContainsKey("last"))
                    {
                        result = result + " Missing 'last' arg.";
                    }
                    if (!Parameters.ContainsKey("pass"))
                    {
                        result = result + " Missing 'pass' arg.";
                    }
                    return($"<error>arguments: {result}</error>");
                }
            }
            // Note: formerly undocumented functionality!! (gwyneth 20220414)
            else if (Method == "server_quit")
            {
                if (parts.Length < 2)
                {
                    return($"<error>{Method}: missing 'pass' arg.</error>");
                }
                if (parts[1] == Program.config.security.serverPass)
                {
                    if (Sessions != null)
                    {
                        foreach (KeyValuePair <UUID, Session> s in Sessions)
                        {
                            lock (Sessions) DisposeSession(s.Key);
                        }
                        StillRunning = false;
                        // note: a caveat of this undocumented method is that it requires a _new_
                        // incoming request to actually kill the server... could be a ping, though. (gwyneth 20220414)
                        return("<status>success - all bot sessions were logged out and a request was made for queued shutdown</status>");
                    }
                    else
                    {
                        // it's fine if there are no sessions (gwyneth 20220414)
                        return("<status>success - no sessions were active</status>");
                    }
                }
                else
                {
                    // wrong password sent! (gwyneth 20220414)
                    return($"<error>{Method}: server authentication failure</error>");
                }
            }
            else if (Method == "ping")
            {
                if (parts.Length < 2)
                {
                    return($"<error>{Method}: missing 'pass' arg.</error>");
                }
                if (parts[1] == Program.config.security.serverPass)
                {
                    return($"<{Method}>I'm alive!</{Method}>");
                }
                else
                {
                    // wrong password sent! (gwyneth 20220414)
                    return($"<error>{Method}: server authentication failure</error>");
                }
            }
            else if (Method == "session_list")
            {
                if (parts.Length < 2)
                {
                    return("<error>missing 'pass' arg.</error>");
                }
                if (parts[1] == Program.config.security.serverPass)
                {
                    bool check = false;
                    if (Program.Sessions.Count != 0)                     // no sessions? that's fine, no need to abort
                    {
                        check = true;
                    }

                    string response = $"<{Method}>";
                    if (check)                          // optimisation: if empty, no need to run the foreach (gwyneth 20220424)
                    {
                        foreach (KeyValuePair <OpenMetaverse.UUID, RESTBot.Session> kvp in Program.Sessions)
                        {
                            if (kvp.Value.Bot != null && kvp.Value.Bot.Client != null && kvp.Value.Bot.Client.Self != null && kvp.Value.Bot.Client.Network != null)
                            {
                                response += $@"
	<session>
		<session_id>{kvp.Key.ToString()}</session_id>
		<key>{kvp.Value.Bot.Client.Self.AgentID.ToString()}</key>
		<name>{kvp.Value.Bot.Client.Self.FirstName} {kvp.Value.Bot.Client.Self.LastName}</name>
		<FirstName>{kvp.Value.Bot.Client.Self.FirstName}</FirstName>
		<LastName>{kvp.Value.Bot.Client.Self.LastName}</LastName>
		<status>{kvp.Value.Bot.myStatus.ToString()}</status>
		<uptime>{kvp.Value.Bot.getUptimeISO8601()}</uptime>
		<start>{kvp.Value.Bot.Start}</start>
		<CurrentSim>{kvp.Value.Bot.Client.Network.CurrentSim.ToString()}</CurrentSim>
		<Position>{kvp.Value.Bot.Client.Self.SimPosition.X},{kvp.Value.Bot.Client.Self.SimPosition.Y},{kvp.Value.Bot.Client.Self.SimPosition.Z}</Position>
		<Rotation>{kvp.Value.Bot.Client.Self.SimRotation.X},{kvp.Value.Bot.Client.Self.SimRotation.Y},{kvp.Value.Bot.Client.Self.SimRotation.Z},{kvp.Value.Bot.Client.Self.SimRotation.W}</Rotation>
	</session>"    ;
                            }
                            else
                            {
                                // Somehow, we have a session ID that has no bot assigned;
                                // this should never be the case, but... (gwyneth 20220426)
                                response += $"<session><session_id>{kvp.Key.ToString()}</session_id><key>{UUID.Zero.ToString()}</key></session>";
                            }
                        }
                    }
                    else
                    {
                        response += "no sessions";
                    }
                    response += $"</{Method}>";
                    return(response);
                }
                else
                {
                    // wrong password sent! (gwyneth 20220414)
                    return($"<error>{Method}: server authentication failure</error>");
                }
            }
            else if (Method == "stats")
            {
                if (parts.Length < 2)
                {
                    return($"<error>{Method}: missing 'pass' arg.</error>");
                }
                if (parts[1] == Program.config.security.serverPass)
                {
                    string response = "<stats><bots>" + ((Sessions != null) ? Sessions.Count.ToString() : "0") + "</bots>"
                                      + "<uptime>" + (DateTime.Now - uptime) + "</uptime></stats>";
                    return(response);
                }
                else
                {
                    return($"<error>{Method}: server authentication failure</error>");
                }
            }

            //Only a method? pssh.
            if (parts.Length == 1)
            {
                return("<error>no session key found</error>");
            }

            UUID sess = new UUID();

            try
            {
                sess = new UUID(parts[1]);
            }
            catch (FormatException)
            {
                return("<error>cannot parse the session key</error>");
            }
            catch (Exception e)
            {
                DebugUtilities.WriteError(e.Message);
            }

            //Session checking
            if (!ValidSession(sess, headers.Hostname))
            {
                return("<error>invalidsession</error>");
            }

            //YEY PROCESSING
            RestBot?r = null;

            if (Sessions != null)
            {
                r = Sessions[sess].Bot;
            }

            if (r == null)
            {
                return($"<error>no RestBot found for session {sess.ToString()}</error>");
            }
            //Last accessed for plugins
            if (Sessions != null)
            {
                Sessions[sess].LastAccessed = DateTime.Now;
            }
            //Pre-error checking
            if (r.myStatus != RestBot.Status.Connected) //Still logging in?
            {
                return($"<error>{r.myStatus.ToString()}</error>");
            }
            else if (!r.Client.Network.Connected) //Disconnected?
            {
                return("<error>clientdisconnected</error>");
            }
            else if (Method == "exit")
            {
                DisposeSession(sess);
                return("<disposed>true</disposed>");
            }
            else if (Method == "stats")
            {
                string response = "<bots>" + ((Sessions != null) ? Sessions.Count.ToString() : "NaN") + "</bots>";
                response += "<uptime>" + (DateTime.Now - uptime) + "</uptime>";
                return(response);
            }

            return(r.DoProcessing(Parameters, parts));
        } // end DoProcessing
Esempio n. 5
0
        Process(RestBot b, Dictionary <string, string> Parameters)
        {
            Utils
            .LongToUInts(b.Client.Network.CurrentSim.Handle,
                         out regionX,
                         out regionY);

            try
            {
                double
                    x             = 0.0f,
                    y             = 0.0f,
                    z             = 0.0f;
                bool   check      = true;
                bool   run        = false;
                string avatarName = String.Empty;

                if (Parameters.ContainsKey("avatar"))
                {
                    avatarName = Parameters["avatar"].ToString().Replace("+", " ");
                }
                else
                {
                    check = false;
                }

                // added it here, but I'm not sure if it's correct... (gwyneth 20220409)
                if (Parameters.ContainsKey("run"))
                {
                    check &= bool.TryParse(Parameters["run"], out run);
                }

                if (!check)
                {
                    return($"<error>{MethodName} parameters have to be avatar name to follow</error>");
                }

                if (run)
                {
                    b.Client.Self.Movement.AlwaysRun = true;
                }
                else
                {
                    b.Client.Self.Movement.AlwaysRun = false;
                }

                lock (b.Client.Network.Simulators)
                {
                    for (int i = 0; i < b.Client.Network.Simulators.Count; i++)
                    {
                        DebugUtilities
                        .WriteDebug($"Found {b.Client.Network.Simulators[i].ObjectsAvatars.Count} Avatar(s)");
                        target =
                            b
                            .Client
                            .Network
                            .Simulators[i]
                            .ObjectsAvatars
                            .Find(delegate(Avatar avatar)
                        {
                            DebugUtilities.WriteDebug($"Found avatar: {avatar.Name}");
                            return(avatar.Name == avatarName);
                        });

                        if (target != null)
                        {
                            goalPos = target.Position;
                        }
                        else
                        {
                            DebugUtilities
                            .WriteError($"{MethodName} - Error finding position of '{avatarName}'");
                            return($"<error>{MethodName} error finding position of '{avatarName}'</error>");
                        }
                    }
                }

                goalPos = new Vector3((float)x, (float)y, (float)z);
                b.Client.Self.Movement.TurnToward(goalPos, false);

                // C# 8+ is stricter with nulls; if 'me' is null, we got a problem,
                // because there is no way to calculate the distance; so we simply set it to zero
                // in that case; this may play havoc with the algorithm, though. (gwyneth 20220213)
                if (me != null)
                {
                    prevDistance = Vector3.Distance(goalPos, me.Client.Self.SimPosition);
                }
                else
                {
                    prevDistance = 0;
                }
                if (prevDistance > 30)
                {
                    b.Client.Self.Movement.AlwaysRun = true;
                }
                else
                {
                    b.Client.Self.Movement.AlwaysRun = false;
                }

                // Convert the local coordinates to global ones by adding the region handle parts to x and y
                b
                .Client
                .Self
                .AutoPilot(goalPos.X + regionX, goalPos.Y + regionY, goalPos.Z);
                Active = true;

                while (Active)
                {
                    Thread.Sleep(1 * 1000);
                }
                DebugUtilities.WriteSpecial($"End {MethodName} Thread!");
                return($"<{MethodName}>{b.Client.Self.GlobalPosition.X},{b.Client.Self.GlobalPosition.Y},{b.Client.Self.GlobalPosition.Z}</{MethodName}>");
            }
            catch (Exception e)
            {
                DebugUtilities.WriteError(e.Message);
                return($"<error>{MethodName}: {e.Message}</error>");
            }
        }
Esempio n. 6
0
        Process(RestBot b, Dictionary <string, string> Parameters)
        {
            uint
                regionX,
                regionY;

            Utils
            .LongToUInts(b.Client.Network.CurrentSim.Handle,
                         out regionX,
                         out regionY);

            try
            {
                double
                    x        = 0.0f,
                    y        = 0.0f,
                    z        = 0.0f,
                    distance = 0.0f;
                bool check   = true;
                /// <summary>Selects between running (true) or walking (false)</summary>
                bool run = false;

                if (Parameters.ContainsKey("x"))
                {
                    check &= double.TryParse(Parameters["x"], out x);
                    DebugUtilities.WriteDebug($"{MethodName} Parse: {Parameters["x"]} into {x}");
                }
                else
                {
                    check = false;
                }

                if (Parameters.ContainsKey("y"))
                {
                    check &= double.TryParse(Parameters["y"], out y);
                }
                else
                {
                    check = false;
                }

                if (Parameters.ContainsKey("z"))
                {
                    check &= double.TryParse(Parameters["z"], out z);
                }
                else
                {
                    check = false;
                }

                if (Parameters.ContainsKey("distance"))
                {
                    check &= double.TryParse(Parameters["distance"], out distance);
                }

                if (Parameters.ContainsKey("run"))
                {
                    check &= bool.TryParse(Parameters["run"], out run);
                }

                if (!check)
                {
                    return($"<error>{MethodName} parameters have to be x, y, z, [distance, run]</error>");
                }

                if (run)
                {
                    b.Client.Self.Movement.AlwaysRun = true;
                }
                else
                {
                    b.Client.Self.Movement.AlwaysRun = false;
                }
                goalPos = new Vector3((float)x, (float)y, (float)z);
                b.Client.Self.Movement.TurnToward(goalPos, false);

                // Check for null and, if so, abort with error (gwyneth 20220213)
                if (me == null)
                {
                    DebugUtilities.WriteError("'me' was null!");
                    return($"<error>{MethodName}: 'me' was null</error>");
                }

                prevDistance = Vector3.Distance(goalPos, me.Client.Self.SimPosition);

                // Convert the local coordinates to global ones by adding the region handle parts to x and y
                b
                .Client
                .Self
                .AutoPilot(goalPos.X + regionX, goalPos.Y + regionY, goalPos.Z);
                Active   = true;
                attempts = 20;                  // reset attempts, or else they'll be stuck at zero... (gwyneth 20220304)
                while (Active)
                {
                    Thread.Sleep(1 * 1000);
                }
                DebugUtilities.WriteSpecial($"End {MethodName} Thread!");
                return($"<{MethodName}>{b.Client.Self.GlobalPosition.X},{b.Client.Self.GlobalPosition.Y},{b.Client.Self.GlobalPosition.Z}</{MethodName}>");
            }
            catch (Exception e)
            {
                DebugUtilities.WriteError(e.Message);
                return($"<error>{MethodName}: {e.Message}</error>");
            }
        }
Esempio n. 7
0
        /// <summary>
        /// Login block
        /// </summary>
        public LoginReply Login()
        {
            LoginReply response = new LoginReply();

            DebugUtilities.WriteSpecial("Login block was called in Login()");
            if (Client.Network.Connected)
            {
                DebugUtilities.WriteError("Uhm, Login() was called when we where already connected. Hurr");
                return(new LoginReply());
            }

            //Client.Network.LoginProgress +=
            //    delegate(object? sender, LoginProgressEventArgs e)
            //    {
            //        DebugUtilities.WriteDebug($"Login {e.Status}: {e.Message}");
            //        if (e.Status == LoginStatus.Success)
            //        {
            //            DebugUtilities.WriteSpecial("Logged in successfully");
            //            myStatus = Status.Connected;
            //            response.wasFatal = false;
            //            response.xmlReply = "<success><session_id>" + sessionid.ToString() + "</session_id></success>";
            //        }
            //        else if (e.Status == LoginStatus.Failed)
            //        {
            //            DebugUtilities.WriteError("$There was an error while connecting: {Client.Network.LoginErrorKey}");
            //            response.wasFatal = true;
            //            response.xmlReply = "<error></error>";
            //        }
            //    };
            // Optimize the throttle
            Client.Throttle.Wind  = 0;
            Client.Throttle.Cloud = 0;
            Client.Throttle.Land  = 1000000;
            Client.Throttle.Task  = 1000000;

            // we add this check here because LOGIN_SERVER should never be assigned null (gwyneth 20220213)
            if (Program.config != null && Program.config.networking.loginuri != null)
            {
                Client.Settings.LOGIN_SERVER = Program.config.networking.loginuri;                      // could be String.Empty, so we check below...
            }
            else if (RESTBot.XMLConfig.Configuration.defaultLoginURI != null)
            {
                Client.Settings.LOGIN_SERVER = RESTBot.XMLConfig.Configuration.defaultLoginURI;                 // could ALSO be String.Empty, so we check below...
            }
            else
            {
                Client.Settings.LOGIN_SERVER = String.Empty;
            }

            // Any of the above _might_ have set LOGIN_SERVER to an empty string, so we check first if we have
            // something inside the string. (gwyneth 20220213)
            // To-do: validate the URL first? It's not clear if .NET 6 already does that at some point...
            if (Client.Settings.LOGIN_SERVER == String.Empty)
            {
                // we don't know where to login to!
                response.wasFatal = true;
                response.xmlReply = "<error fatal=\"true\">No login URI provided</error>";
                DebugUtilities.WriteError("No login URI provided; aborting...");
                return(response);
            }
            DebugUtilities.WriteDebug($"Login URI: {Client.Settings.LOGIN_SERVER}");

            LoginParams loginParams =
                Client.Network.DefaultLoginParams(First, Last, MD5Password, "RestBot", Program.Version);

            loginParams.Start = Start;

            if (Client.Network.Login(loginParams))
            {
                DebugUtilities.WriteSpecial($"{First} {Last} logged in successfully");
                myStatus          = Status.Connected;
                response.wasFatal = false;
                response.xmlReply =
                    $@"<success>
	<session_id>{sessionid.ToString()}</session_id>
	<key>{Client.Self.AgentID.ToString()}</key>
	<name>{Client.Self.FirstName} {Client.Self.LastName}</name>
	<FirstName>{Client.Self.FirstName}</FirstName>
	<LastName>{Client.Self.LastName}</LastName>
	<CurrentSim>{Client.Network.CurrentSim.ToString()}</CurrentSim>
	<Position>{Client.Self.SimPosition.X},{Client.Self.SimPosition.Y},{Client.Self.SimPosition.Z}</Position>
	<Rotation>{Client.Self.SimRotation.X},{Client.Self.SimRotation.Y},{Client.Self.SimRotation.Z},{Client.Self.SimRotation.W}</Rotation>
</success>";
            }
            else
            {
                DebugUtilities
                .WriteError($"There was an error while connecting: {Client.Network.LoginErrorKey}");
                switch (Client.Network.LoginErrorKey)
                {
                case "connect":
                case "key":
                case "disabled":
                    response.wasFatal = true;
                    response.xmlReply =
                        $"<error fatal=\"true\">{Client.Network.LoginMessage}</error>";
                    break;

                case "presence":
                case "timed out":
                case "god":
                    DebugUtilities
                    .WriteWarning("Nonfatal error while logging in.. this may be normal");
                    response.wasFatal = false;
                    response.xmlReply =
                        $"<error fatal=\"false\">{Client.Network.LoginMessage}</error><retry>10</retry><session_id>{sessionid}</session_id>";

                    DebugUtilities
                    .WriteSpecial("Relogin attempt will be made in 10 minutes");
                    ReloginTimer.Interval = 10 * 60 * 1000;                             //10 minutes
                    ReloginTimer.Start();
                    break;

                default:
                    DebugUtilities
                    .WriteError($"{sessionid.ToString()} UNKNOWN ERROR {Client.Network.LoginErrorKey} WHILE ATTEMPTING TO LOGIN");
                    response.wasFatal = true;
                    response.xmlReply =
                        $"<error fatal=\"true\">Unknown error '{Client.Network.LoginErrorKey}' has occurred.</error>";
                    break;
                }

                if (response.wasFatal == false)
                {
                    myStatus = Status.Reconnecting;
                }
            }

            //Client.Network.BeginLogin(loginParams);
            return(response);
        }         // end Login()