[C#] Remote Administration Control Panel

Status
Not open for further replies.
Junior Spellweaver
Joined
Jun 8, 2012
Messages
114
Reaction score
49
I'm currently making a new Administraiton Control Panel for a server.

The project is on-going since the server owner might want me to make more tools for it.

The panel currently holds the following features:
  • Login Authentication
  • Player Online Count
  • Search Entire Database
  • Edit Search Results
  • Remotely Restart Server

Is there anything that you guys think I should add?

Screenshots (Directly from Visual Studio):
- Login
- Main Panel
- Execute Search Query (Characters>Search on Main Panel)
- Easy to navigate search results.
- Server View of Restarting Server before connections
- Server and Client view of server restarting.
 
How is that funny? C# is an object oriented language. I'm honestly kind of fed up with people dissing on C#. It's truly a great language and I could argue all night in it's defense, however it mostly comes down to preferences and the overall goals of the project.
 
Here's a snippet of code from Log.cs, a class file which I made for reading configurations, error logging, and hashing MD5.

PHP:
using System;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Xml;
using System.Security.Cryptography;

namespace flyff_acp
{
    class Log
    {
        private string[] confs = new string[5];
        private string conf_path = Application.StartupPath + @"\configs\";
        private string error_path = Application.StartupPath + @"\logs\";
        private string pass_salt;

        public string ReadConfigs()
        {
            try
            {
                XmlTextReader Reader = new XmlTextReader(conf_path + "conf.xml");

                try
                {
                    while (Reader.Read())
                    {
                        switch (Reader.Name)
                        {
                            case "server":
                                confs[0] = Reader.ReadString();
                                break;

                            case "username":
                                confs[1] = Reader.ReadString();
                                break;

                            case "password":
                                confs[2] = Reader.ReadString();
                                break;

                            case "database":
                                confs[3] = Reader.ReadString();
                                break;

                            case "salt":
                                pass_salt = Reader.ReadString();
                                break;
                        }
                    }

                    try
                    {
                        return "Data Source=" + confs[0] + ";User Id=" + confs[1] + ";Password=" + confs[2] + ";Initial Catalog=" + confs[3] + ";";
                    }

                    catch
                    {
                        ErrorLog("Unable to compile the configurations and initialize the connection string to return. Please ensure you have all four elements needed.");
                        throw new Exception("Unable to compile configurations and create connection string.");
                    }
                }

                catch
                {
                    Reader.Close();
                    ErrorLog("Unable to compile the configurations. Please ensure that your configuration file (" + conf_path + "\\conf.xml) is properly formatted.");
                    throw new Exception("Unable to compile configurations.");
                }
            }

            catch
            {
                ErrorLog("Unable to read the configurations (" + conf_path + "\\conf.xml)! Ensure that the program has read privilages from the computer and that the file exists and is properly formatted.");
                throw new Exception("Unable to read configurations.");
            }
        }

        public void ErrorLog(string message)
        {
            if (File.Exists(error_path + "error_log.txt") != true)
            {
                Directory.CreateDirectory(error_path);
                FileStream create = File.Create(error_path + "error_log.txt");
                create.Close();
            }

            using (StreamWriter write = File.AppendText(error_path + "error_log.txt"))
            {
                write.WriteLine("[{0}]: {1}", DateTime.Now.ToString(), message);
            }
        }

        public string hash(string input)
        {
            MD5CryptoServiceProvider x = new MD5CryptoServiceProvider();
            byte[] bs = Encoding.UTF8.GetBytes(pass_salt + input);
            bs = x.ComputeHash(bs);
            StringBuilder s = new StringBuilder();
            foreach (byte b in bs)
            {
                s.Append(b.ToString("x2").ToLower());
            }
            return s.ToString();
        }
    }
}

Here's the code from the "Information" form. This form was responsible for showing and grouping the data for easy data updating and viewing.
PHP:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Data.SqlClient;
using System.Windows.Forms;

namespace flyff_acp
{
    public partial class Info : Form
    {
        private ArrayList sql_data;
        private ArrayList sql_columns;
        private string table_name;

        private List<Label> labels = new List<Label>();
        private List<TextBox> textboxes = new List<TextBox>();
        private int left_constant_lbl = 13;
        private int center_constant_lbl = 425;
        private int right_constant_lbl = 219;
        private int interval = 48;

        private int left_constant_txt = 16;
        private int center_constant_txt = 428;
        private int right_constant_txt = 222;

        private string current_side = "left";
        private int new_y_lbl = 15;
        private int new_y_txt = 31;

        private int current_id = -1;

        private string update;
        private string where;

        public Info(string tablename, ArrayList data, ArrayList columns)
        {
            InitializeComponent();

            table_name = tablename;

            sql_data = data;
            sql_columns = columns;
            if (sql_data != null)
            {
                showAs.Items.AddRange(sql_columns.ToArray());
                showAs.SelectedIndex = 0;
                reloadListBox(0);
            }
            else
            {
                no_data_lbl.Visible = true;
            }
        }

        private void reloadListBox(int index)
        {
            data_values.Items.Clear();

            foreach (object[] item in sql_data)
            {
                data_values.Items.Add(item[index]);
            }
        }

        private void showAs_SelectedIndexChanged(object sender, EventArgs e)
        {
            reloadListBox(showAs.SelectedIndex);
        }

        private void data_values_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (current_id != data_values.SelectedIndex)
            {
                foreach (Label lbl in labels)
                {
                    this.Controls.Remove(lbl);
                }

                foreach (TextBox txtbox in textboxes)
                {
                    this.Controls.Remove(txtbox);
                }
                current_id = data_values.SelectedIndex;
                current_side = "left";
                new_y_lbl = 15;
                new_y_txt = 31;

                reloadData();

                for (int i = 0; i < textboxes.Count; i++)
                {
                    TextBox txt = textboxes[i];
                    Label lbl = labels[i];

                    if (i == textboxes.Count - 1)
                    {
                        where += lbl.Text + "='" + txt.Text + "'";
                    }
                    else
                    {
                        where += lbl.Text + "='" + txt.Text + "' AND ";
                    }
                }
            }
        }

        private void reloadData()
        {
            for (int i = 0; i < sql_columns.Count; i++)
            {
                string current_column = sql_columns[i].ToString();
                
                Label temp = new Label();
                TextBox temp_txt = new TextBox();
                
                if (current_side == "left")
                {
                    temp.Location = new System.Drawing.Point(left_constant_lbl, new_y_lbl);
                    temp_txt.Location = new System.Drawing.Point(left_constant_txt, new_y_txt);
                    current_side = "center";
                }
                else
                if (current_side == "center")
                {
                    temp.Location = new System.Drawing.Point(center_constant_lbl, new_y_lbl);
                    temp_txt.Location = new System.Drawing.Point(center_constant_txt, new_y_txt);
                    current_side = "right";
                }
                else
                {
                    temp.Location = new System.Drawing.Point(right_constant_lbl, new_y_lbl);
                    temp_txt.Location = new System.Drawing.Point(right_constant_txt, new_y_txt);
                    new_y_lbl = new_y_lbl + interval;
                    new_y_txt = new_y_txt + interval;
                    current_side = "left";
                }
                
                temp.Name = current_column + "_lbl";
                temp.Text = current_column;
                temp.Visible = true;
                temp.Size = new System.Drawing.Size(temp.PreferredWidth, temp.PreferredHeight);

                temp_txt.Name = current_column + "_txt";

                temp_txt.Text = (string)((object[])sql_data[current_id])[i].ToString();

                temp_txt.Visible = true;
                temp_txt.Size = new System.Drawing.Size(171, 20);

                labels.Add(temp);
                textboxes.Add(temp_txt);

                this.Controls.Add(temp);
                this.Controls.Add(temp_txt);
            }
        }

        private void Update_Btn_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < textboxes.Count; i++)
            {
                TextBox txt = textboxes[i];
                Label lbl = labels[i];

                if (i == textboxes.Count - 1)
                {
                    update += lbl.Text + "='" + txt.Text + "'";
                }
                else
                {
                    update += lbl.Text + "='" + txt.Text + "', ";
                }
            }

            Log Logs = new Log();

            string query = "UPDATE " + table_name + " SET " + update + " WHERE " + where;

            string con_string = Logs.ReadConfigs();

            SqlConnection con = new SqlConnection(con_string);
            try
            {
                con.Open();
            }
            catch
            {
                Logs.ErrorLog("Unable to create a connection to the SQL server on the Information form.");
                throw new Exception("Unable to connect to the SQL server.");
            }

            SqlCommand cmd = new SqlCommand(query, con);

            try
            {
                cmd.ExecuteNonQuery();

                MessageBox.Show("Data updated!");
                this.Close();
            }

            catch
            {
                Logs.ErrorLog("Unable to execute a query on the Information form. Query: " + query);
                throw new Exception("Unable to execute query.");
            }
        }
    }
}
 
Last edited:
I suggest you go a little more advanced with it and use a server/client approach instead of a direct database connection.

I agree with this. Unless the database has good indexing setup (unlikely, since tutorials tend to use MSSQL Express) and the website is optimized when it comes to database access (for example, DB Query Caching), you'd just be adding 1 more thing contending for database access.
 
I agree with this. Unless the database has good indexing setup (unlikely, since tutorials tend to use MSSQL Express) and the website is optimized when it comes to database access (for example, DB Query Caching), you'd just be adding 1 more thing contending for database access.

Well like Lethal mentioned in another thread, it really is best to have the database only accessible by the localhost for security reasons. On top of that it can help your database handle the load better if multiple administrators are using it. Not to mention C# is a managed language so unless your willing to pay some serious cash for some good obfuscation or a packer like themida you shouldn't let things like mssql connection strings go inside a program that the public could possibly get a hold of. In the case that the program were to get leaked the person who got a hold of it would still have no authentication.
 
Last edited:
All suggestions taken into consideration.

Considering that my C# isn't great to the point where I can make applications such as Mootie's, I'm sure my project won't go necessarily as far as monitoring servers and communicating with the server(such as sending packets to World or Certifier), but it can obviously do basics such as communicating with the database.

Do you think it may be more helpful and/or resource-friendly to have a server/client connection with queries running from client to server and the server sending back results?
 
Last edited:
Do you think it may be more helpful and/or resource-friendly to have a server/client connection with queries running from client to server and the server sending back results?

Yes, this is what they were saying. This is because on the server program, you can manage the SQL connection in 1 place, allowing you to have better security on it (no outside access to db) and control the queries so that you don't have a lot of simultaneous db access.
 
Also if you write this in a server/client you can do more than just send queries to be executed by the database.
For example if you set up a packaging system:

Let's say the first byte is going to be your packet header. Your packet header will tell the server or client what needs to be processed. Very recommended to specify constants for the packet headers like this:

Code:
public const byte

       //[Client To Server]
       DatabaseQueryRequest = 0x01,
       CommandQueryRequest = 0x02,

       //[Server To Client]  
       DatabaseQueryResponse = 0x03,
       CommandQueryResponse = 0x04;

In your receive method you would read the first byte of the incoming data and throw it in a switch statement:

Code:
     //Server Sided Receive

     byte PacketHeader = ReceiveBuffer[0];

     switch(PacketHeader)
     {
           case DatabaseQueryRequest: HandleDatabaseQuery(ReceiveBuffer, ClientSocket); break;
           case CommandQueryRequest: HandleCommandQuery(ReceiveBuffer, ClientSocket); break;
           
           //For example purposes I passed all of the data received into the method that
           //would handle the packaged data. This is not recommended however I did not
           //want to give too much information about the BG Data Packaging API away. 
     }

Your methods could than handle the rest of the data like so:

Code:
      //Server Sided Handling Methods
      
      public void HandleDatabaseQuery(byte[] ReceiveBuffer, Socket ClientSocket)
      {
            //Read the query from the byte array
            //Clean the query if you want to
            //Execute Query
           
            //Create SendBuffer array to store response data.
            //SendBuffer[0] = DatabaseQueryResponse;
            //Fill the array with any data you would like to return.
            //Send array to client socket.
      }

This is where the data packaging api really comes in handy since with a little knowledge of memory and data conversion you can write methods to add or read data to a byte array. Using BG's api the code would look similar too:

Code:
      public void HandleDatabaseQuery(ReceivePacket rp, Connection conn)
      {
            SendPacket sp = new SendPacket();
            sp.Addbyte(DatabaseQueryResponse);

            string Query = rp.Readstring();
            boolean ReturnData = rp.Readbool();
            //Clean Query

            try
            {
                 //Execute Query
                 sp.Addbool(true);

                 //There is a much better way of doing this but it goes past the scope of this
                 //example.
                 foreach(Result)
                      sp.Addstring(Result);
            }
            catch(Exception e)
            {
                  sp.Addbool(false);
                  sp.Addstring(e.Message);
            }

             conn.Send(sp);
      }

Package Class Example:
Code:
package PacketSystem;

/**
 *
 * @author Gage Orsburn
 */

public class Packet 
{
    private byte[] Data = null;
    private int Position = 0;
    private boolean BigEndian = true;
    
    /*****************************************/
    
    public Packet()
    {
        Data = new byte[2048];
    }
    
    public Packet(int Size)
    {
        Data = new byte[Size];
    }
    
    public Packet(byte[] b)
    {
        Data = b;
    }
    
    public Packet(Packet p)
    {
        Data = p.getData();
    }
    
    /*****************************************/
    
    public Packet(boolean b)
    {
        Data = new byte[2048];
        BigEndian = b;
    }
    
    public Packet(int Size, boolean b)
    {
        Data = new byte[Size];
        BigEndian = b;
    }
    
    public Packet(byte[] b, boolean c)
    {
        Data = b;
        BigEndian = c;
    }
    
    public Packet(Packet p, boolean b)
    {
        Data = p.getData();
        BigEndian = b;
    }
    
    /*****************************************/
    
    public byte[] getData()
    {
        return Data;
    }
    
    /*****************************************/
    
    public void setBigEndian()
    {
        BigEndian = true;
    }
    
    public void setLittleEndian()
    {
        BigEndian = false;
    }
    
    /*****************************************/
    
    public void addByte(byte b)
    {
        Data[Position] = b;
        Position++;
    }
    
    public void addBoolean(boolean b)
    {
        Data[Position] = (byte)(b ? 1 : 0);
        Position++;
    }
    
    public void addShort(short s)
    {
        if(BigEndian)
        {
            addByte((byte)(s >> 8));
            addByte((byte)(s     ));
        }
        else
        {
            addByte((byte)(s     ));
            addByte((byte)(s >> 8));
        }
    }
    
    public void addShort(int i)
    {
        if(BigEndian)
        {
            addByte((byte)(i >> 8));
            addByte((byte)(i     ));
        }
        else
        {
            addByte((byte)(i     ));
            addByte((byte)(i >> 8));
        }
    }
    
    public void addCharacter(char c)
    {
        //addShort((short)c);
        addByte((byte)c);
    }
    
    public void addInteger(int i)
    {
        if(BigEndian)
        {
            addByte((byte)(i >> 24));
            addByte((byte)(i >> 16));
            addByte((byte)(i >> 8 ));
            addByte((byte)(i      ));
        }
        else
        {
            addByte((byte)(i      ));
            addByte((byte)(i >> 8 ));
            addByte((byte)(i >> 16));
            addByte((byte)(i >> 24));
        }
    }
    
    public void addLong(long l)
    {
        if(BigEndian)
        {
            addByte((byte)(l >> 56));
            addByte((byte)(l >> 48));
            addByte((byte)(l >> 40));
            addByte((byte)(l >> 32));
            addByte((byte)(l >> 24));
            addByte((byte)(l >> 16));
            addByte((byte)(l >> 8 ));
            addByte((byte)(l      ));
        }
        else
        {
            addByte((byte)(l      ));
            addByte((byte)(l >> 8 ));
            addByte((byte)(l >> 16));
            addByte((byte)(l >> 24));
            addByte((byte)(l >> 32));
            addByte((byte)(l >> 40));
            addByte((byte)(l >> 48));
            addByte((byte)(l >> 56));
        }
    }
    
    public void addString(String s)
    {
        char[] charArray = s.toCharArray();
        int Length = charArray.length;
        
        addInteger(Length);
        
        for(char c : charArray)
            addCharacter(c);
    }
    
    public void addBytes(byte[] ba)
    {
        addInteger(ba.length);
        
        for(byte b : ba)
            addByte(b);
    }
    
    /*****************************************/
    
    public byte readByte()
    {
        return Data[Position++];
    }
    
    
    public boolean readBoolean()
    {
        return (Data[Position++] == 1 ? true : false);
    }
    
    public short readShort()
    {
        if(BigEndian)
        {
            return (short)((short)(readByte() << 8) + (readByte()));
        }
        else
        {
            return (short)((short)(readByte()) + (readByte() << 8));
        }
    }
    
    public char readCharacter()
    {
        return (char)readByte();
    }
    
    public int readInteger()
    {
        if(BigEndian)
        {
            return (readByte() << 24) + ((readByte() & 0xFF) << 16) + ((readByte() & 0xFF) << 8) + ((readByte() & 0xFF));
        }
        else
        {
            return (readByte() & 0xFF) + ((readByte() & 0xFF) << 8) + ((readByte() & 0xFF) << 16) + (readByte() << 24);
        }
    }
    
    public long readLong()
    {
        if(BigEndian)
        {
            return ((long)(readInteger()) << 32) + (readInteger() & 0xFFFFFFFFL);
        }
        else
        {
            return (readInteger() & 0xFFFFFFFFL) + ((long)(readInteger()) << 32);
        }
    }
    
    public String readString()
    {
        String temp = "";
        int Length = readInteger();
        
        for(int i = 0; i < Length; i++)
            temp += readCharacter();
        
        return temp;
    }

    public byte[] readBytes()
    {
        int Length = readInteger();
        byte[] temp = new byte[Length];
		
        for(int i = 0; i < Length; i++)
            temp[i] = readByte();
        
        return temp;
    }
}
 
Last edited:
Alrighty then.

I'm going to currently be working on a new approach to redo the administration panel and have it communicate solely by server and client communications via tcp connection.

Are there any suggestions as to what I could add to this panel (feature wise)?
 
Its a good idea, and it might be worth the work. BUT I'm crying when I look at the code snippets...

EDIT: Some "feature requests":
- use SSL-Sockets to make the connection a bit saver (you're working with sensitive data)
- You should use a database abstraction layer (linq2sql or Entity Framework) and pass the data from the server to the client ("no direct database connection"), this should prevent exploits and increase performance + its easier to code
- You should go for a nicer GUI (eg. Visual Studio like GUI would be nice (there are tutorials on the net for that))
- - Maybe even go for a web application using https...
 
Last edited:
Status
Not open for further replies.
Back