To get technical:
The reason we recommend using the list command when using timer/utimer is to avoid "double substitution and evaulation". This problem arises because of the way variable and command substitutions are done in tcl.
Contrary to many other programming languages, when you prefix a variable name with $ in tcl, this causes the tcl interpreter to read the variable and replace the $variable with it's content. Same goes for command substitutions ( [command] ). In essence, the command will never see the variable, let alone be aware of it's existence.
The problem with timer/utimer, is that the command line argument gets passed to the tcl interpreter as the timer expires. As such, the interpreter will first check for any variable and command substitutions before executing the command. Hence, we got a second substitution.
The effect of this is perhaps best illustrated with an example:
Code: Select all
#Set tmpnick to contain #evil[die]nick
set tmpnick "evil\[die\]nick"
#msg $tmpnick Hello1!
puthelp "PRIVMSG $tmpnick :Hello1!"
#Now lets create a timer that msg's $tmpnick Hello2! after 5 secondsutimer 5 "puthelp \"PRIVMSG $tmpnick :Hello2!\""
Here one might think that we've been careful and escaped everything properly. For a simple case, such as the Hello1! msg, that would be true. The tmpnick variable is substituted and the command will send an msg to evil[die]nick.
However, in the second case, we don't execute the puthelp command instantly, but store it for later execution:
Code: Select all
#timer command after all substitutions are done:
utimer 5 {puthelp "PRIVMSG evil[die]nick :Hello2!"}
When the timer expires, the second argument is sent to eval:
Code: Select all
eval {puthelp "PRIVMSG evil[die]nick :Hello2!"}
puthelp "PRIVMSG evil[die]nick :Hello2!"
#the interpreter sees [die], and does a command substitution, calling die.
#Your eggdrop dies...
So, why don't we just escape the $ in the first case?
Well, that would work in this simple example, but consider this one, where namespaces becomes a factor:
Code: Select all
proc myproc {nickname} {
utimer 5 "puthelp \"PRIVMSG \$nickname :Hello3!\""
}
myproc $tmpnick
Here, we call myproc with the content of tmpnick (same as prev. example), so within myproc,
nickname will contain that malicious nickname. We escaped the $ so it will not be evaluated immediately, thus we do not have an issue with double evaluation.
Code: Select all
#timer command after all substitutions are done:
utimer 5 {puthelp "PRIVMSG $nickname :Hello3!"}
#and when the timer expires..
eval {puthelp "PRIVMSG $nickname :Hello3!"}
puthelp "PRIVMSG $nickname :Hello3!"
puthelp {PRIVMSG eggdrop :Hello3!}
#This time, we got "lucky", as nickname happens to be defined, and contain your eggdrop's current nickname.
# Had it been anything else, we'd possibly disclose sensitive configuration settings,
# or end up trying to read a non-existant variable.
The idea of using tcl-lists, is that it's a data structure that is designed to preserve contents from being "mangled" by substitutions.
Code: Select all
proc mysafeproc {nickname} {
utimer 5 [list puthelp "PRIVMSG $nickname :Hello4!"]
}
mysafeproc $tmpnick
First thing, we have a command substitution (list). Within this, the variable will also be substituted:
Code: Select all
list puthelp "PRIVMSG $nickname :Hello4!
#this returns: puthelp {PRIVMSG evil[die]nick :Hello4!}
utimer 5 {puthelp {PRIVMSG evil[die]nick :Hello4!}}
#as the timer expires:
eval {puthelp {PRIVMSG evil[die]nick :Hello4!}}
puthelp {PRIVMSG evil[die]nick :Hello4!}
#Since the "PRIVMSG ..." argument is enclosed with {} rather than "", the tcl interpreter will skip any further substitutions within this argument.
#Puthelp will thus send the message to evil[die]nick