# # Well, I implemented a pop3 server in Eggdrop Tcl, so I figured, what # the hell, might as well do SMTP. (I think datamorph gave me the idea.) # # It takes a special transport map to actually get mail through to the bot, # since it can't bind to port 25. My Postfix transport(5) map has a line that # looks like this: # # .BOT smtp::1125 # # Any mail sent to user@my.BOT then gets delivered through this script. # # Due to limitations in Eggdrop's note mechanisms, only the last line of # the message is delivered. (This is probably a good thing.) The sender # is prepended to the message. # # If a user no longer wishes to receive notes via email, one can use the # .+noteign command on the Eggdrop. Ideally, that would return a 500 error # when trying to send mail to them, but there's no "getnoteigns" Tcl command, # and sendnote doesn't return an error. # # Note we only speak SMTP, not ESMTP. # # -- # Jun 2005, Jack Cuervo # set smtpStateInfo() "" set helpTopics(MAIL) "MAIL FROM:
" append helpTopics(MAIL) "\n\tSpecifies message sender." set helpTopics(RCPT) "RCPT TO:
" append helpTopics(MAIL) "\n\tSpecifies message recipients." append helpTopics(MAIL) "\n\tOnly the local-part of the address is used." set helpTopics(HELO) "HELO " append helpTopics(HELO) "\n\tStrikes up a conversation." proc smtp:putlog {stuff} { putloglev 6 * $stuff } proc smtp:putdcc {idx stuff} { putdcc $idx "$stuff\r" } proc smtp:kill {idx} { global smtpStateInfo if {[valididx $idx]} { smtp:putdcc $idx "221 Closing connection." killdcc $idx } smtp:putlog "SMTP: DISCONNECT: $smtpStateInfo($idx,host)" array unset smtpStateInfo $idx } proc smtp:handleData {idx data} { global smtpStateInfo set smtpStateInfo($idx,body) $data } proc smtp:searchState {idx state} { global smtpStateInfo foreach wantState [split $state ","] { set hasState 0 foreach currentState [split $smtpStateInfo($idx,state) ","] { if {$wantState == $currentState} { incr hasState } } if {! $hasState} { return 0 } } return 1 } proc smtp:addState {idx state} { global smtpStateInfo return [set smtpStateInfo($idx,state) [join "$smtpStateInfo($idx,state) $state" ","]] } proc smtp:removeState {idx state} { global smtpStateInfo set newState "" foreach killState [split $state ","] { foreach currentState [split $smtpStateInfo($idx,state) ","] { if {$killState != $currentState} { lappend newState $currentState } } } set smtpStateInfo($idx,state) [join $newState ","] } proc smtp:endMessage {idx} { global smtpStateInfo if {![smtp:searchState $idx "HELO,MAIL,RCPT"]} { smtp:putdcc $idx "503-Need HELO,MAIL,RCPT before DATA" smtp:putdcc $idx "503 Go read the RFC, dipshit" return } foreach recipient [split $smtpStateInfo($idx,recipient) ","] { if {! [sendnote "SMTP" $recipient [join "$smtpStateInfo($idx,sender) {$smtpStateInfo($idx,body)}" ": "]]} { smtp:putlog "SMTP: Message delivery FAILURE: $smtpStateInfo($idx,sender) -> $recipient" smtp:putdcc $idx "500 Couldn't deliver message" } else { smtp:putlog "SMTP: Message delivered: $smtpStateInfo($idx,sender) -> $recipient" smtp:putdcc $idx "250 Message delivered" } } smtp:removeState $idx "DATA" } proc smtp:command {idx data} { global smtpStateInfo if {$data == ""} { smtp:kill $idx return } if {[smtp:searchState $idx "DATA"]} { if {$data == "."} { smtp:endMessage $idx } else { smtp:handleData $idx $data } return } set command [string toupper [lindex $data 0]] set isValid 0 foreach validCommand [info commands smtpCommands:*] { set vc [string range $validCommand [expr [string first ":" $validCommand] + 1] end] if {$vc == $command} { incr isValid break } } if {! $isValid} { smtp:putdcc $idx "502 Command \"$command\" unimplemented" return } smtpCommands:$command $idx [lrange $data 1 end] } proc smtp:greet {idx} { global botnet-nick version foreach conn [dcclist SOCKET] { if {[lindex $conn 0] == $idx} { global smtpHostInfo smtpStateInfo set smtpStateInfo($idx,host) [lindex $conn 2] set smtpStateInfo($idx,state) "CONNECT" smtp:putlog "SMTP: CONNECT: $smtpStateInfo($idx,host)" break } } smtp:putdcc $idx "220 ${botnet-nick} SMTP Eggdrop/[lindex $version 0]" control $idx smtp:command } proc smtpCommands:EHLO {idx data} { smtp:putdcc $idx "500-Did I /say/ ESMTP? NO. I DID NOT." smtp:putdcc $idx "500 Try HELO instead of EHLO." } proc smtpCommands:HELO {idx data} { global smtpStateInfo set smtpStateInfo($idx,state) "HELO" smtp:putdcc $idx "250 Hi there." } proc smtpCommands:MAIL {idx data} { global smtpStateInfo if {! [smtp:searchState $idx "HELO"]} { smtp:putdcc $idx "503 Need HELO first." return } if {[set liar [lindex [split $data ":"] 1]] == ""} { smtp:putdcc $idx "500 Invalid address" return } regsub -all {[ <>]} $liar "" smtpStateInfo($idx,sender) smtp:putdcc $idx "250 Whatever you say, $smtpStateInfo($idx,sender)" smtp:addState $idx "MAIL" } proc smtpCommands:RCPT {idx data} { global smtpStateInfo if {! [smtp:searchState $idx "HELO"]} { smtp:putdcc $idx "503 Who the fuck are you?" return } if {[set recipient [lindex [split $data ":"] 1]] == ""} { smtp:putdcc $idx "500 Invalid address" return } regsub -all {[ <>]} $recipient "" recipient set recipient [lindex [split $recipient "@"] 0] if {! [validuser $recipient]} { smtp:putdcc $idx "550 I don't know $recipient" return } smtp:putdcc $idx "250 OK: $recipient" set recipient [join $smtpStateInfo($idx,recipient) ","] set smtpStateInfo($idx,recipient) $recipient smtp:addState $idx "RCPT" } proc smtpCommands:DATA {idx data} { if {! [smtp:searchState $idx "HELO,MAIL,RCPT"]} { smtp:putdcc $idx "503 You need HELO,MAIL,RCPT first" return } smtp:putdcc $idx "354 Only the last line will be delivered" smtp:addState $idx "DATA" } proc smtpCommands:HELP {idx data} { global helpTopics if {$data == ""} { smtp:putdcc $idx "200-HELP topics:" smtp:putdcc $idx "200-[join [array names helpTopics] " "]" smtp:putdcc $idx "200 Use HELP for more information." } else { set data [string toupper $data] foreach helpTopic [array names helpTopics] { if {$data == $helpTopic} { smtp:putdcc $idx "200-[join [split $helpTopics($helpTopic) "\n"] "\r\n200-"]" smtp:putdcc $idx "200 End of HELP $data" return } } smtp:putdcc $idx "500 No HELP for $data" } return } proc smtpCommands:QUIT {idx data} { smtp:kill $idx } set smtpsock [listen 1125 script smtp:greet pub]