For test purposes, let's make a new DM environment. For the sake of consistence, we'll name it "Test". In this new environment, we'll need a code file and an interface file.
The interface will consist of one output (set to default), one input (set to default) and four buttons. If any of you know Visual Basic, you were probably taught to name your controls after what they simulate. This is entirely up to you, but I tend to agree with this method.
Our four buttons will have the IDs "button_command1", "button_command2", "button_command3" and "button_command4". We'll also want to name them accordingly so we know what button we're pressing. Once you've done that, you'll hopefully have something that looks like this.
Now if you use verbs to control your buttons, chances are you'd edit your buttons command to contain the name of your verb. Here's where we get sneaky, we're not going to do that. All our buttons commands will be assigned in our code.
First things first, we need to assign a few definitions for our program. I don't know how many of you out there do this, but I find it helps for debugging things, and redesigning interface elements based on new windows. It is by no means necessary.
So what we're going to do now, is make definitions for our window ID and our button IDs.
//Our windows ID is default. So that's what we set our MAIN_WINDOW definition to.
#define MAIN_WINDOW "default"
//Now we define our button IDs.
#define COMMAND1 "button_command1"
#define COMMAND2 "button_command2"
#define COMMAND3 "button_command3"
#define COMMAND4 "button_command4"
Now we've done that, we need to set a client variable, we'll name it "generated_id". This ID is going to be affixed to all the commands, so if a user types our command into the command line, it wont execute.
//So this is our generated_id. We aren't going to set it a value yet, we'll do that when the user logs in.
client/var/generated_id = null
Now we have a generated ID. But the problem is, we have no ID. The ID can be whatever you desire, but if we want to try and make it as secure as possible, I figure we'll use a hash. Who's likely to guess a randomly generated 32 character string?
So in order to use a hash, we'll have to write a proc to generate one.
proc
generate_hash(client/C)
/*
We want the hash to be fairly random. So we'll put the users ckey in there,
then the current time stamp, and just for fun, we'll add a random number between 1 and 100.
This should hopefully runin them when it comes time to guess our hash.
*/
C.generated_id = md5("[C.ckey][world.timeofday][rand(1,100)]")
Righto, no we have a generated_id variable for our client, and a proc to generate a hash for the user. Now it's time to put it all into action.
This will require another proc, what we're going to do now is, assign all our buttons their rightful commands. To keep in tone with the tutorial, we'll name their commands "command1" - "command4".
proc
assign_commands(client/C)
/*
This is where our definitions come into play. In case we want to change a command ID later on
or rebuild a window without deleting our current. We can just change the definition value to match.
The first thing we want to do is generate our client's hash. You'll see why we do this here
instead of client/New() in a bit.
*/
generate_hash(C) //Generate Client Hash.
winset(C, "[MAIN_WINDOW].[COMMAND1]", "command=\"[C.generated_id].command1\"") //Assign a command for button 1
winset(C, "[MAIN_WINDOW].[COMMAND2]", "command=\"[C.generated_id].command2\"") //Assign a command for button 2
winset(C, "[MAIN_WINDOW].[COMMAND3]", "command=\"[C.generated_id].command3\"") //Assign a command for button 3
winset(C, "[MAIN_WINDOW].[COMMAND4]", "command=\"[C.generated_id].command4\"") //Assign a command for button 4
Right. To summarise what we've done so far: We've made our interface with a default output and input, and we've added four buttons with the ID's button_command1 - button_command4. Afterwards, we made a client variable called "generated_id" and wrote two procs, one to assign a hash to the clients id variable and another to call the generate_hash() proc and assign the commands to the buttons.
As you can see from the code above, we put the hash in the command before the actual command ".command1". This means, if a user was to try and execute the command, they'd have to know the hash first.
Now what we need to do to make that flourish, is call the assign_command() proc in client/New(). Let's do that now:
client
New()
assign_commands(src)
..()
This means the buttons will now have commands assigned to them. Because we didn't define the commands in the interface, the user doesn't know what those commands are, and can't execute them when we don't want them too. And if they do manage to discover the commands, they need to know their hash (which at the moment, will change every time they login), which is security step number two.
Now we need to make the commands actually do something. We're going to do this using client/Command(), as stated above. So let's write our client/Command() code.
client
Command(C as command_text)
/*
First thing we're going to do is check to see if the first thirty-two characters of the command they executed
is equal to our hash.
if(first 32 characters are equal to clients ID) execute this code
else echo the command.
That's how it's going to work.
Although a hash is always thirty-two characters, if you really want to keep them on their toes.
You could use several methods that don't sometimes include hashes, sometimes phrases.
If we use length() on the generated_id, we know its size. Making the possibilities limitless.
*/
if(copytext(C, 1, length(src.generated_id)+1) == src.generated_id)
/*
Now we're sure the command is a button, because the hash matches. Now we're going to switch()
the remainder of the string to execute the desired command.
If they did manage to guess the hash but not the command, nothing will happen.
We added two to the id length because in our assign proc, we separated the hash and the command with a period.
We will have to take this into account.
*/
switch(copytext(C, length(src.generated_id)+2))
if("command1") world << "You executed Command 1"
if("command2") world << "You executed Command 2"
if("command3") world << "You executed Command 3"
if("command4") world << "You executed Command 4"
else world << "[src]: [C]"
..()
Now let's look at the full script in its completed form.
//Window Definitions.
#define MAIN_WINDOW "default"
//Button Definitions.
#define COMMAND1 "button_command1"
#define COMMAND2 "button_command2"
#define COMMAND3 "button_command3"
#define COMMAND4 "button_command4"
client/var/generated_id = null
proc
generate_hash(client/C)
C.generated_id = md5("[C.ckey][world.timeofday][rand(1,100)]")
assign_commands(client/C)
generate_hash(C) //Generate Client Hash.
winset(C, "[MAIN_WINDOW].[COMMAND1]", "command=\"[C.generated_id].command1\"")
winset(C, "[MAIN_WINDOW].[COMMAND2]", "command=\"[C.generated_id].command2\"")
winset(C, "[MAIN_WINDOW].[COMMAND3]", "command=\"[C.generated_id].command3\"")
winset(C, "[MAIN_WINDOW].[COMMAND4]", "command=\"[C.generated_id].command4\"")
client
New()
assign_commands(src)
..()
Command(C as command_text)
if(copytext(C, 1, length(src.generated_id)+1) == src.generated_id)
switch(copytext(C, length(src.generated_id)+2))
if("command1") world << "You executed Command 1"
if("command2") world << "You executed Command 2"
if("command3") world << "You executed Command 3"
if("command4") world << "You executed Command 4"
else world << "[src]: [C]"
..()
That's lookin' mighty pretty. Now we have functional buttons, that are hashed and nearing the impossible mark to successfully guess. This means users can't execute our commands when we don't want them too. Bonus for us!
Now this script as is will work just how we want it to. But Alathon mentioned something to me previously when he and I had a discussion on this technique, and he suggested that we should regenerate the hash every time a command is executed. This will make it absolutely impossible for the user to break our security.
This part isn't necessary unless you really are that paranoid about people executing commands when you don't want them too.
This is the reason we don't generate the hash when the client logs in. Because where it's placed now, is where it'll have to be placed for the rest of this script to work out. So we just saved ourselves a meaningless proc call. Go us!
Anyway, to regenerate the hash every time the user executes a command, we simply recall the assign_commands() proc under the switch() block in client command. This will force a new hash to generate, and apply it to all the commands.
//Window Definitions.
#define MAIN_WINDOW "default"
//Button Definitions.
#define COMMAND1 "button_command1"
#define COMMAND2 "button_command2"
#define COMMAND3 "button_command3"
#define COMMAND4 "button_command4"
client/var/generated_id = null
proc
generate_hash(client/C)
C.generated_id = md5("[C.ckey][world.timeofday][rand(1,100)]")
assign_commands(client/C)
generate_hash(C) //Generate Client Hash.
winset(C, "[MAIN_WINDOW].[COMMAND1]", "command=\"[C.generated_id].command1\"")
winset(C, "[MAIN_WINDOW].[COMMAND2]", "command=\"[C.generated_id].command2\"")
winset(C, "[MAIN_WINDOW].[COMMAND3]", "command=\"[C.generated_id].command3\"")
winset(C, "[MAIN_WINDOW].[COMMAND4]", "command=\"[C.generated_id].command4\"")
client
New()
assign_commands(src)
..()
Command(C as command_text)
if(copytext(C, 1, length(src.generated_id)+1) == src.generated_id)
switch(copytext(C, length(src.generated_id)+2))
if("command1") world << "You executed Command 1"
if("command2") world << "You executed Command 2"
if("command3") world << "You executed Command 3"
if("command4") world << "You executed Command 4"
assign_commands(src) //Force another hash to generate when the command is finished executing.
else world << "[src]: [C]"
..()
You can download the full script for this tutorial: Here.