/usr/share/tcltk/tcllib1.17/smtpd/smtpd.tcl is in tcllib 1.17-dfsg-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 | # smtpd.tcl - Copyright (C) 2001 Pat Thoyts <patthoyts@users.sourceforge.net>
#
# This provides a minimal implementation of the Simple Mail Tranfer Protocol
# as per RFC821 and RFC2821 (http://www.normos.org/ietf/rfc/rfc821.txt) and
# is designed for use during local testing of SMTP client software.
#
# -------------------------------------------------------------------------
# This software is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the file 'license.terms' for
# more details.
# -------------------------------------------------------------------------
# @mdgen EXCLUDE: clients/mail-test.tcl
package require Tcl 8.3; # tcl minimum version
package require logger; # tcllib 1.3
package require mime; # tcllib
package provide smtpd 1.5
namespace eval ::smtpd {
variable version [package present smtpd]
variable stopped
namespace export start stop configure
variable commands
if {![info exists commands]} {
set commands {EHLO HELO MAIL RCPT DATA RSET NOOP QUIT HELP}
# non-minimal commands HELP VRFY EXPN VERB ETRN DSN
}
variable extensions
if {! [info exists extensions]} {
array set extensions {
8BITMIME {}
SIZE 0
}
}
variable options
if {! [info exists options]} {
array set options {
serveraddr {}
deliverMIME {}
deliver {}
validate_host {}
validate_sender {}
validate_recipient {}
usetls 0
tlsopts {}
}
set options(banner) "tcllib smtpd $version"
}
variable tlsopts {-cadir -cafile -certfile -cipher
-command -keyfile -password -request -require -ssl2 -ssl3 -tls1}
variable log
if {![info exists log]} {
set log [logger::init smtpd]
${log}::setlevel warn
proc ${log}::stdoutcmd {level text} {
variable service
puts "\[[clock format [clock seconds] -format {%H:%M:%S}]\
$service $level\] $text"
}
}
variable Help
if {![info exists Help]} {
array set Help {
{} {{Topics:} { HELO MAIL DATA RSET NOOP QUIT}
{For more information use "HELP <topic>".}}
HELO {{HELO <hostname>} { Introduce yourself.}}
MAIL {{MAIL FROM: <sender> [ <parameters> ]}
{ Specify the sender of the message.}
{ If using ESMTP there may be additional parameters of the}
{ form NAME=VALUE.}}
DATA {{DATA} { Send your mail message.}
{ End with a line containing a single dot.}}
RSET {{RSET} { Reset the session.}}
NOOP {{NOOP} { Command ignored by server.}}
QUIT {{QUIT} { Exit SMTP session}}
}
}
}
# -------------------------------------------------------------------------
# Description:
# Obtain configuration options for the server.
#
proc ::smtpd::cget {option} {
variable options
variable tlsopts
variable log
set optname [string trimleft $option -]
if { [string equal option -loglevel] } {
return [${log}::currentloglevel]
} elseif { [info exists options($optname)] } {
return $options($optname)
} elseif {[lsearch -exact $tlsopts -$optname] != -1} {
set ndx [lsearch -exact $options(tlsopts) -$optname]
if {$ndx != -1} {
return [lindex $options(tlsopts) [incr ndx]]
}
return {}
} else {
return -code error "unknown option \"-$optname\": \
must be one of -[join [array names options] {, -}]"
}
}
# -------------------------------------------------------------------------
# Description:
# Configure server options. These include validation of hosts or users
# and a procedure to handle delivery of incoming mail. The -deliver
# procedure must handle mail because the server may release all session
# resources once the deliver proc has completed.
# An example might be to exec procmail to deliver the mail to users.
#
proc ::smtpd::configure {args} {
variable options
variable commands
variable extensions
variable log
variable tlsopts
if {[llength $args] == 0} {
set r [list -loglevel [${log}::currentloglevel]]
foreach {opt value} [array get options] {
lappend r -$opt $value
}
lappend r -
return $r
}
while {[string match -* [set option [lindex $args 0]]]} {
switch -glob -- $option {
-loglevel {${log}::setlevel [Pop args 1]}
-deliverMIME {set options(deliverMIME) [Pop args 1]}
-deliver {set options(deliver) [Pop args 1]}
-validate_host {set options(validate_host) [Pop args 1]}
-validate_sender {set options(validate_sender) [Pop args 1]}
-validate_recipient {set options(validate_recipient) [Pop args 1]}
-banner {set options(banner) [Pop args 1]}
-usetls {
set usetls [Pop args 1]
if {$usetls && ![catch {package require tls}]} {
set options(usetls) 1
set extensions(STARTTLS) {}
lappend commands STARTTLS
}
}
-- { Pop args; break }
default {
set failed 1
if {[lsearch $tlsopts $option] != -1} {
set options(tlsopts) \
[concat $options(tlsopts) $option [Pop args 1]]
set failed 0
}
set msg "unknown option: \"$option\":\
must be one of -deliverMIME, -deliver,\
-validate_host, -validate_recipient,\
-validate_sender or an option suitable\
to tls::init"
if {$failed} {
return -code error $msg
}
}
}
Pop args
}
return {}
}
# -------------------------------------------------------------------------
# Description:
# Start the server on the given interface and port.
#
proc ::smtpd::start {{myaddr {}} {port 25}} {
variable options
variable stopped
if {[info exists options(socket)]} {
return -code error \
"smtpd service already running on socket $options(socket)"
}
if {$myaddr != {}} {
set options(serveraddr) $myaddr
set myaddr "-myaddr $myaddr"
} else {
if {$options(serveraddr) == {}} {
set options(serveraddr) [info hostname]
}
}
set options(socket) [eval socket \
-server [namespace current]::accept $myaddr $port]
set stopped 0
Log notice "smtpd service started on $options(socket)"
return $options(socket)
}
# -------------------------------------------------------------------------
# Description:
# Stop a running server. Do nothing if the server isn't running.
#
proc ::smtpd::stop {} {
variable options
variable stopped
if {[info exists options(socket)]} {
close $options(socket)
set stopped 1
Log notice "smtpd service stopped"
unset options(socket)
}
}
# -------------------------------------------------------------------------
# Description:
# Accept a new connection and setup a fileevent handler to process the new
# session. Performs a host id validation step before allowing access.
#
proc ::smtpd::accept {channel client_addr client_port} {
variable options
variable version
upvar [namespace current]::state_$channel State
# init state array
catch {unset State}
initializeState $channel
set State(access) allowed
set State(client_addr) $client_addr
set State(client_port) $client_port
set accepted true
# configure the data channel
fconfigure $channel -buffering line -translation crlf -encoding ascii
fileevent $channel readable [list [namespace current]::service $channel]
# check host access permissions
if {[cget -validate_host] != {}} {
if {[catch {eval [cget -validate_host] $client_addr} msg] } {
Log notice "access denied for $client_addr:$client_port: $msg"
Puts $channel "550 Access denied: $msg"
set State(access) denied
set accepted false
}
}
if {$accepted} {
# Accept the connection
Log notice "connect from $client_addr:$client_port on $channel"
Puts $channel "220 $options(serveraddr) $options(banner); [timestamp]"
}
return
}
# -------------------------------------------------------------------------
# Description:
# Initialize the channel state array. Called by accept and RSET.
#
proc ::smtpd::initializeState {channel} {
upvar [namespace current]::state_$channel State
set State(indata) 0
set State(to) {}
set State(from) {}
set State(data) {}
set State(options) {}
}
# -------------------------------------------------------------------------
# Description:
# Access the state of a connected session using the channel name as part
# of the state array name. Called with no value, it returns the current
# value of the item (or {} if not defined).
#
proc ::smtpd::state {channel args} {
if {[llength $args] == 0} {
return [array get [namespace current]::state_$channel]
}
set arrname [namespace current]::[subst state_$channel]
if {[llength $args] == 1} {
set r {}
if {[info exists [subst $arrname]($args)]} {
# FRINK: nocheck
set r [set [subst $arrname]($args)]
}
return $r
}
foreach {name value} $args {
# FRINK: nocheck
set [namespace current]::[subst state_$channel]($name) $value
}
return {}
}
# -------------------------------------------------------------------------
# Description:
# Pop the nth element off a list. Used in options processing.
#
proc ::smtpd::Pop {varname {nth 0}} {
upvar $varname args
set r [lindex $args $nth]
set args [lreplace $args $nth $nth]
return $r
}
# -------------------------------------------------------------------------
# Description:
# Wrapper to call our log procedure.
#
proc ::smtpd::Log {level text} {
variable log
${log}::${level} $text
}
# -------------------------------------------------------------------------
# Description:
# Safe puts.
# If the client closes the channel, then puts will throw an error. Lets
# terminate the session if this occurs.
proc ::smtpd::Puts {channel args} {
if {[catch {uplevel puts $channel $args} msg]} {
Log error $msg
catch {
close $channel
# FRINK: nocheck
unset -- [namespace current]::state_$channel
}
}
return $msg
}
# -------------------------------------------------------------------------
# Description:
# Perform the chat with a connected client. This procedure accepts input on
# the connected socket and executes commands according to the state of the
# session.
#
proc ::smtpd::service {channel} {
variable commands
variable options
upvar [namespace current]::state_$channel State
if {[eof $channel]} {
close $channel
return
}
if {[catch {gets $channel cmdline} msg]} {
close $channel
Log error $msg
return
}
if { $cmdline == "" && [eof $channel] } {
Log warn "client has closed the channel"
return
}
Log debug "received: $cmdline"
# If we are handling a DATA section, keep looking for the end of data.
if {$State(indata)} {
if {$cmdline == "."} {
set State(indata) 0
fconfigure $channel -translation crlf
if {[catch {deliver $channel} err]} {
# permit delivery handler to return SMTP errors in errorCode
if {[regexp {\d{3}} $::errorCode]} {
Puts $channel "$::errorCode $err"
} else {
Puts $channel "554 Transaction failed: $err"
}
} else {
Puts $channel "250 [state $channel id]\
Message accepted for delivery"
}
} else {
# RFC 2821 section 4.5.2: Transparency
if {[string match {..*} $cmdline]} {
set cmdline [string range $cmdline 1 end]
}
lappend State(data) $cmdline
}
return
}
# Process SMTP commands (case insensitive)
set cmd [string toupper [lindex [split $cmdline] 0]]
if {[lsearch $commands $cmd] != -1} {
if {[info proc $cmd] == {}} {
Puts $channel "500 $cmd not implemented"
} else {
# If access denied then client can only issue QUIT.
if {$State(access) == "denied" && $cmd != "QUIT" } {
Puts $channel "503 bad sequence of commands"
} else {
set r [eval $cmd $channel [list $cmdline]]
}
}
} else {
Puts $channel "500 Invalid command"
}
return
}
# -------------------------------------------------------------------------
# Description:
# Generate a random ASCII character for use in mail identifiers.
#
proc ::smtpd::uidchar {} {
set c .
while {! [string is alnum $c]} {
set n [expr {int(rand() * 74 + 48)}]
set c [format %c $n]
}
return $c
}
# Description:
# Generate a unique random identifier using only ASCII alphanumeric chars.
#
proc ::smtpd::uid {} {
set r {}
for {set cn 0} {$cn < 12} {incr cn} {
append r [uidchar]
}
return $r
}
# -------------------------------------------------------------------------
# Description:
# Calculate the local offset from GMT in hours for use in the timestamp
#
proc ::smtpd::gmtoffset {} {
set now [clock seconds]
set local [clock format $now -format "%j %H" -gmt false]
set zulu [clock format $now -format "%j %H" -gmt true]
set lh [expr {([scan [lindex $local 0] %d] * 24) \
+ [scan [lindex $local 1] %d]}]
set zh [expr {([scan [lindex $zulu 0] %d] * 24) \
+ [scan [lindex $zulu 1] %d]}]
set off [expr {$lh - $zh}]
set off [format "%+03d00" $off]
return $off
}
# -------------------------------------------------------------------------
# Description:
# Generate a standard SMTP compliant timestamp. That is a local time but with
# the timezone represented as an offset.
#
proc ::smtpd::timestamp {} {
set ts [clock format [clock seconds] \
-format "%a, %d %b %Y %H:%M:%S" -gmt false]
append ts " " [gmtoffset]
return $ts
}
# -------------------------------------------------------------------------
# Description:
# Get the servers ip address (from http://purl.org/mini/tcl/526.html)
#
proc ::smtpd::server_ip {} {
set me [socket -server xxx -myaddr [info hostname] 0]
set ip [lindex [fconfigure $me -sockname] 0]
close $me
return $ip
}
# -------------------------------------------------------------------------
# Description:
# deliver is called once a mail transaction is completed and there is
# no deliver procedure defined
# The configured -deliverMIME procedure is called with a MIME token.
# If no such callback is defined then try the -deliver option and use
# the old API.
#
proc ::smtpd::deliver {channel} {
set deliverMIME [cget deliverMIME]
if { $deliverMIME != {} \
&& [state $channel from] != {} \
&& [state $channel to] != {} \
&& [state $channel data] != {} } {
# create a MIME token from the mail message.
set tok [mime::initialize -string \
[join [state $channel data] "\n"]]
# mime::setheader $tok "From" [state $channel from]
# foreach recipient [state $channel to] {
# mime::setheader $tok "To" $recipient -mode append
# }
# catch and rethrow any errors.
set err [catch {eval $deliverMIME [list $tok]} msg]
mime::finalize $tok -subordinates all
if {$err} {
Log debug "error in deliver: $msg"
return -code error -errorcode $::errorCode \
-errorinfo $::errorInfo $msg
}
} else {
# Try the old interface
deliver_old $channel
}
}
# -------------------------------------------------------------------------
# Description:
# Deliver is called once a mail transaction is completed (defined as the
# completion of a DATA command). The configured -deliver procedure is called
# with the sender, list of recipients and the text of the mail.
#
proc ::smtpd::deliver_old {channel} {
set deliver [cget deliver]
if { $deliver != {} \
&& [state $channel from] != {} \
&& [state $channel to] != {} \
&& [state $channel data] != {} } {
if {[catch {$deliver [state $channel from] \
[state $channel to] \
[state $channel data]} msg]} {
Log debug "error in deliver: $msg"
return -code error -errorcode $::errorCode \
-errorinfo $::errorInfo $msg
}
}
}
# -------------------------------------------------------------------------
proc ::smtpd::split_address {address} {
set start [string first < $address]
set end [string last > $address]
set addr [string range $address $start $end]
incr end
set opts [string trim [string range $address $end end]]
return [list $addr $opts]
}
# -------------------------------------------------------------------------
# The SMTP Commands
# -------------------------------------------------------------------------
# Description:
# Initiate an SMTP session
# Reference:
# RFC2821 4.1.1.1
#
proc ::smtpd::HELO {channel line} {
variable options
if {[state $channel domain] != {}} {
Puts $channel "503 bad sequence of commands"
Log debug "HELO received out of sequence."
return
}
set r [regexp -nocase {^HELO\s+([-\w\.]+)\s*$} $line -> domain]
if {$r == 0} {
Puts $channel "501 Syntax error in parameters or arguments"
Log debug "HELO received \"$line\""
return
}
Puts $channel "250 $options(serveraddr) Hello $domain\
\[[state $channel client_addr]\], pleased to meet you"
state $channel domain $domain
Log debug "HELO on $channel from $domain"
return
}
# -------------------------------------------------------------------------
# Description:
# Initiate an ESMTP session
# Reference:
# RFC2821 4.1.1.1
proc ::smtpd::EHLO {channel line} {
variable options
variable extensions
if {[state $channel domain] != {}} {
Puts $channel "503 bad sequence of commands"
Log debug "EHLO received out of sequence."
return
}
set r [regexp -nocase {^EHLO\s+([-\w\.]+)\s*$} $line -> domain]
if {$r == 0} {
Puts $channel "501 Syntax error in parameters or arguments"
Log debug "EHLO received \"$line\""
return
}
Puts $channel "250-$options(serveraddr) Hello $domain\
\[[state $channel client_addr]\], pleased to meet you"
foreach {extn opts} [array get extensions] {
Puts $channel [string trimright "250-$extn $opts"]
}
Puts $channel "250 Ready for mail."
state $channel domain $domain
Log debug "EHLO on $channel from $domain"
return
}
# -------------------------------------------------------------------------
# Description:
# Reference:
# RFC2821 4.1.1.2
#
proc ::smtpd::MAIL {channel line} {
set r [regexp -nocase {^MAIL FROM:\s*(.*)} $line -> from]
if {$r == 0} {
Puts $channel "501 Syntax error in parameters or arguments"
Log debug "MAIL received \"$line\""
return
}
if {[catch {
set from [split_address $from]
set opts [lindex $from 1]
set from [lindex $from 0]
eval array set addr [mime::parseaddress $from]
# RFC2821 3.7: we must accept null return path addresses.
if {[string equal "<>" $from]} {
set addr(error) {}
}
} msg]} {
set addr(error) $msg
}
if {$addr(error) != {} } {
Log debug "MAIL failed $addr(error)"
Puts $channel "501 Syntax error in parameters or arguments"
return
}
if {[cget -validate_sender] != {}} {
if {[catch {eval [cget -validate_sender] $addr(address)}]} {
# this user has been denied
Log info "MAIL denied user $addr(address)"
Puts $channel "553 Requested action not taken:\
mailbox name not allowed"
return
}
}
Log debug "MAIL FROM: $addr(address)"
state $channel from $from
state $channel options $opts
Puts $channel "250 OK"
return
}
# -------------------------------------------------------------------------
# Description:
# Specify a recipient for this mail. This command may be executed multiple
# times to contruct a list of recipients. If a -validate_recipient
# procedure is configured then this is used. An error from the validation
# procedure indicates an invalid or unacceptable mailbox.
# Reference:
# RFC2821 4.1.1.3
# Notes:
# The postmaster mailbox MUST be supported. (RFC2821: 4.5.1)
#
proc ::smtpd::RCPT {channel line} {
set r [regexp -nocase {^RCPT TO:\s*(.*)} $line -> to]
if {$r == 0} {
Puts $channel "501 Syntax error in parameters or arguments"
Log debug "RCPT received \"$line\""
return
}
if {[catch {
set to [split_address $to]
set opts [lindex $to 1]
set to [lindex $to 0]
eval array set addr [mime::parseaddress $to]
} msg]} {
set addr(error) $msg
}
if {$addr(error) != {}} {
Log debug "RCPT failed $addr(error)"
Puts $channel "501 Syntax error in parameters or arguments"
return
}
if {[string match -nocase "postmaster" $addr(local)]} {
# we MUST support this recipient somehow as mail.
Log notice "RCPT to postmaster"
} else {
if {[cget -validate_recipient] != {}} {
if {[catch {eval [cget -validate_recipient] $addr(address)}]} {
# this recipient has been denied
Log info "RCPT denied mailbox $addr(address)"
Puts $channel "553 Requested action not taken:\
mailbox name not allowed"
return
}
}
}
Log debug "RCPT TO: $addr(address)"
set recipients {}
catch {set recipients [state $channel to]}
lappend recipients $to
state $channel to $recipients
Puts $channel "250 OK"
return
}
# -------------------------------------------------------------------------
# Description:
# Begin accepting data for the mail payload. A line containing a single
# period marks the end of the data and the server will then deliver the
# mail. RCPT and MAIL commands must have been executed before the DATA
# command.
# Reference:
# RFC2821 4.1.1.4
# Notes:
# The DATA section is the only part of the protocol permitted to use non-
# ASCII characters and non-CRLF line endings and some clients take
# advantage of this. Therefore we change the translation option on the
# channel and reset it once the DATA command is completed. See the
# 'service' procedure for the handling of DATA lines.
# We also insert trace information as per RFC2821:4.4
#
proc ::smtpd::DATA {channel line} {
variable version
upvar [namespace current]::state_$channel State
Log debug "DATA"
if { $State(from) == {}} {
Puts $channel "503 bad sequence: no sender specified"
} elseif { $State(to) == {}} {
Puts $channel "503 bad sequence: no recipient specified"
} else {
Puts $channel "354 Enter mail, end with \".\" on a line by itself"
set State(id) [uid]
set State(indata) 1
lappend trace "Return-Path: $State(from)"
lappend trace "Received: from [state $channel domain]\
\[[state $channel client_addr]\]"
lappend trace "\tby [info hostname] with tcllib smtpd ($version)"
if {[info exists State(tls)] && $State(tls)} {
catch {
array set t [::tls::status $channel]
lappend trace "\t(version=TLS1/SSL3 cipher=$t(cipher) bits=$t(sbits) verify=NO)"
}
}
lappend trace "\tid $State(id); [timestamp]"
set State(data) $trace
fconfigure $channel -translation auto ;# naughty: RFC2821:2.3.7
}
return
}
# -------------------------------------------------------------------------
# Description:
# Reset the server state for this connection.
# Reference:
# RFC2821 4.1.1.5
#
proc ::smtpd::RSET {channel line} {
upvar [namespace current]::state_$channel State
Log debug "RSET on $channel"
if {[catch {initializeState $channel} msg]} {
Log warn "RSET: $msg"
}
Puts $channel "250 OK"
return
}
# -------------------------------------------------------------------------
# Description:
# Verify the existence of a mailbox on the server
# Reference:
# RFC2821 4.1.1.6
#
#proc ::smtpd::VRFY {channel line} {
# # VRFY SP String CRLF
#}
# -------------------------------------------------------------------------
# Description:
# Expand a mailing list.
# Reference:
# RFC2821 4.1.1.7
#
#proc ::smtpd::EXPN {channel line} {
# # EXPN SP String CRLF
#}
# -------------------------------------------------------------------------
# Description:
# Return a help message.
# Reference:
# RFC2821 4.1.1.8
#
proc ::smtpd::HELP {channel line} {
variable Help
set cmd {}
regexp {^HELP\s*(\w+)?} $line -> cmd
if {[info exists Help($cmd)]} {
foreach line $Help($cmd) {
Puts $channel "214-$line"
}
Puts $channel "214 End of HELP"
} else {
Puts $channel "504 HELP topic \"$cmd\" unknown."
}
}
# -------------------------------------------------------------------------
# Description:
# Perform no action.
# Reference:
# RFC2821 4.1.1.9
#
proc ::smtpd::NOOP {channel line} {
set str {}
regexp -nocase {^NOOP (.*)$} -> str
Log debug "NOOP: $str"
Puts $channel "250 OK"
return
}
# -------------------------------------------------------------------------
# Description:
# Terminate a session and close the transmission channel.
# Reference:
# RFC2821 4.1.1.10
# Notes:
# The server is only permitted to close the channel once it has received
# a QUIT message.
#
proc ::smtpd::QUIT {channel line} {
variable options
upvar [namespace current]::state_$channel State
Log debug "QUIT on $channel"
Puts $channel "221 $options(serveraddr) Service closing transmission channel"
close $channel
# cleanup the session state array.
unset State
return
}
# -------------------------------------------------------------------------
# Description:
# Implement support for secure mail transactions using the TLS package.
# Reference:
# RFC3207
# Notes:
#
proc ::smtpd::STARTTLS {channel line} {
variable options
upvar [namespace current]::state_$channel State
Log debug "$line on $channel"
if {![string equal $line STARTTLS]} {
Puts $channel "501 Syntax error (no parameters allowed)"
return
}
if {[lsearch -exact $options(tlsopts) -certfile] == -1
|| [lsearch -exact $options(tlsopts) -keyfile] == -1} {
Puts $channel "454 TLS not available due to temporary reason"
return
}
set import [linsert $options(tlsopts) 0 ::tls::import $channel -server 1]
Puts $channel "220 Ready to start TLS"
if {[catch $import msg]} {
Puts $channel "454 TLS not available due to temporary reason"
} else {
set State(domain) {}; # RFC3207:4.2
set State(tls) 1
}
return
}
# -------------------------------------------------------------------------
# Logging callback for use with tls - you must specify this when configuring
# smtpd if you wan to use it.
#
proc ::smtpd::tlscallback {option args} {
switch -exact -- $option {
"error" {
foreach {chan msg} $args break
Log error "TLS error '$msg'"
}
"verify" {
foreach {chan depth cert rc err} $args break
if {$rc ne "1"} {
Log error "TLS verify/$depth Bad cert '$err' (rc=$rc)"
} else {
array set c $cert
Log notice "TLS verify/$depth: $c(subject)"
}
return $rc
}
"info" {
foreach {chan major minor state msg} $args break
if {$msg ne ""} { append state ": $msg" }
Log debug "TLS ${major}.${minor} $state"
}
default {
Log warn "bad option \"$option\" in smtpd::callback"
}
}
}
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# Local variables:
# mode: tcl
# indent-tabs-mode: nil
# End:
|