smtpd.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014 package require Tcl 8.3;
00015 package require logger;
00016 package require mime;
00017
00018
00019
00020 namespace ::smtpd {
00021 variable rcsid {$Id: smtpd.tcl,v 1.20 2005/12/09 18:27:17 andreas_kupries Exp $}
00022 variable version 1.4.0
00023 variable stopped
00024
00025 namespace export start stop configure
00026
00027 variable commands
00028 if {![info exists commands]} {
00029 commands = {EHLO HELO MAIL RCPT DATA RSET NOOP QUIT HELP}
00030
00031 }
00032
00033 variable extensions
00034 if {! [info exists extensions]} {
00035 array extensions = {
00036 8BITMIME {}
00037 SIZE 0
00038 }
00039 }
00040
00041 variable options
00042 if {! [info exists options]} {
00043 array options = {
00044 serveraddr {}
00045 deliverMIME {}
00046 deliver {}
00047 validate_host {}
00048 validate_sender {}
00049 validate_recipient {}
00050 uls = 0
00051 tlsopts {}
00052 }
00053 }
00054 variable tlsopts {-cadir -cafile -certfile -cipher
00055 -command -keyfile -password -request -require -ssl2 -ssl3 -tls1}
00056
00057 variable log
00058 if {![info exists log]} {
00059 log = [logger::init smtpd]
00060 ${log}::level = warn
00061 ret ${log}::stdoutcmd (type level , type text) {
00062 variable service
00063 puts "\[[clock format [clock seconds] -format {%H:%M:%S}]\
00064 $service $level\] $text"
00065 }
00066 }
00067
00068 variable Help
00069 if {![info exists Help]} {
00070 array Help = {
00071 {} {{Topics:} { HELO MAIL DATA RSET NOOP QUIT}
00072 {For more information use "HELP <topic>".}}
00073 HELO {{HELO <hostname>} { Introduce yourself.}}
00074 MAIL {{MAIL FROM: <sender> [ <parameters> ]}
00075 { Specify the sender of the message.}
00076 { If using ESMTP there may be additional parameters of the}
00077 { form NAME=VALUE.}}
00078 DATA {{DATA} { Send your mail message.}
00079 { End with a line containing a single dot.}}
00080 RSET {{RSET} { Re the = session.}}
00081 NOOP {{NOOP} { Command ignored by server.}}
00082 QUIT {{QUIT} { Exit SMTP session}}
00083 }
00084 }
00085 }
00086
00087
00088
00089
00090
00091 ret ::smtpd::cget (type option) {
00092 variable options
00093 variable tlsopts
00094 variable log
00095 set optname [string trimleft $option -]
00096 if { [string equal option -loglevel] } {
00097 return [${log}::currentloglevel]
00098 } elseif { [info exists options($optname)] } {
00099 return $options($optname)
00100 } elseif {[lsearch -exact $tlsopts -$optname] != -1} {
00101 set ndx [lsearch -exact $options(tlsopts) -$optname]
00102 if {$ndx != -1} {
00103 return [lindex $options(tlsopts) [incr ndx]]
00104 }
00105 return {}
00106 } else {
00107 return -code error "unknown option \"-$optname\": \
00108 must be one of -[join [array names options] {, -}]"
00109 }
00110 }
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120 ret ::smtpd::configure (type args) {
00121 variable options
00122 variable commands
00123 variable extensions
00124 variable log
00125 variable tlsopts
00126
00127 if {[llength $args] == 0} {
00128 set r [list -loglevel [${log}::currentloglevel]]
00129 foreach {opt value} [array get options] {
00130 lappend r -$opt $value
00131 }
00132 lappend r -
00133 return $r
00134 }
00135
00136 while {[string match -* [set option [lindex $args 0]]]} {
00137 switch -glob -- $option {
00138 -loglevel {${log}::setlevel [Pop args 1]}
00139 -deliverMIME {set options(deliverMIME) [Pop args 1]}
00140 -deliver {set options(deliver) [Pop args 1]}
00141 -validate_host {set options(validate_host) [Pop args 1]}
00142 -validate_sender {set options(validate_sender) [Pop args 1]}
00143 -validate_recipient {set options(validate_recipient) [Pop args 1]}
00144 -usetls {
00145 set usetls [Pop args 1]
00146 if {$usetls && ![catch {package require tls}]} {
00147 set options(usetls) 1
00148 set extensions(STARTTLS) {}
00149 lappend commands STARTTLS
00150 }
00151 }
00152 -- { Pop args; break }
00153 default {
00154 set failed 1
00155 if {[lsearch $tlsopts $option] != -1} {
00156 set options(tlsopts) \
00157 [concat $options(tlsopts) $option [Pop args 1]]
00158 set failed 0
00159 }
00160 set msg "unknown option: \"$option\":\
00161 must be one of -deliverMIME, -deliver,\
00162 -validate_host, -validate_recipient,\
00163 -validate_sender or an option suitable\
00164 to tls::init"
00165 if {$failed} {
00166 return -code error $msg
00167 }
00168 }
00169 }
00170 Pop args
00171 }
00172 return {}
00173 }
00174
00175
00176
00177
00178
00179 ret ::smtpd::start (optional myaddr ={) {port 25}} {
00180 variable options
00181 variable stopped
00182
00183 if {[info exists options(socket)]} {
00184 return -code error \
00185 "smtpd service already running on socket $options(socket)"
00186 }
00187
00188 if {$myaddr != {}} {
00189 options = (serveraddr) $myaddr
00190 myaddr = "-myaddr $myaddr"
00191 } else {
00192 if {$options(serveraddr) == {}} {
00193 options = (serveraddr) [info hostname]
00194 }
00195 }
00196
00197 options = (socket) [eval socket \
00198 -server [namespace current]::accept $myaddr $port]
00199 stopped = 0
00200 Log notice "smtpd service started on $options(socket)"
00201 return $options(socket)
00202 }
00203
00204
00205
00206
00207
00208 ret ::smtpd::stop () {
00209 variable options
00210 variable stopped
00211 if {[info exists options(socket)]} {
00212 close $options(socket)
00213 set stopped 1
00214 Log notice "smtpd service stopped"
00215 unset options(socket)
00216 }
00217 }
00218
00219
00220
00221
00222
00223
00224 ret ::smtpd::accept (type channel , type client_, type addr , type client_, type port) {
00225 variable options
00226 variable version
00227 upvar [namespace current]::state_$channel State
00228
00229 # init state array
00230 catch {unset State}
00231 initializeState $channel
00232 set State(access) allowed
00233 set State(client_addr) $client_addr
00234 set State(client_port) $client_port
00235 set accepted true
00236
00237 # configure the data channel
00238 fconfigure $channel -buffering line -translation crlf -encoding ascii
00239 fileevent $channel readable [list [namespace current]::service $channel]
00240
00241 # check host access permissions
00242 if {[cget -validate_host] != {}} {
00243 if {[catch {eval [cget -validate_host] $client_addr} msg] } {
00244 Log notice "access denied for $client_addr:$client_port: $msg"
00245 Puts $channel "550 Access denied: $msg"
00246 set State(access) denied
00247 set accepted false
00248 }
00249 }
00250
00251 if {$accepted} {
00252 # Accept the connection
00253 Log notice "connect from $client_addr:$client_port on $channel"
00254 Puts $channel "220 $options(serveraddr) tcllib smtpd $version; [timestamp]"
00255 }
00256
00257 return
00258 }
00259
00260
00261
00262
00263
00264 ret ::smtpd::initializeState (type channel) {
00265 upvar [namespace current]::state_$channel State
00266 set State(indata) 0
00267 set State(to) {}
00268 set State(from) {}
00269 set State(data) {}
00270 set State(options) {}
00271 }
00272
00273
00274
00275
00276
00277
00278
00279 ret ::smtpd::state (type channel , type args) {
00280 if {[llength $args] == 0} {
00281 return [array get [namespace current]::state_$channel]
00282 }
00283
00284 set arrname [namespace current]::[subst state_$channel]
00285
00286 if {[llength $args] == 1} {
00287 set r {}
00288 if {[info exists [subst $arrname]($args)]} {
00289 # FRINK: nocheck
00290 set r [set [subst $arrname]($args)]
00291 }
00292 return $r
00293 }
00294
00295 foreach {name value} $args {
00296 # FRINK: nocheck
00297 set [namespace current]::[subst state_$channel]($name) $value
00298 }
00299 return {}
00300 }
00301
00302
00303
00304
00305
00306 ret ::smtpd::Pop (type varname , optional nth =0) {
00307 upvar $varname args
00308 set r [lindex $args $nth]
00309 set args [lreplace $args $nth $nth]
00310 return $r
00311 }
00312
00313
00314
00315
00316
00317 ret ::smtpd::Log (type level , type text) {
00318 variable log
00319 ${log}::${level} $text
00320 }
00321
00322
00323
00324
00325
00326
00327 ret ::smtpd::Puts (type channel , type args) {
00328 if {[catch {uplevel puts $channel $args} msg]} {
00329 Log error $msg
00330 catch {
00331 close $channel
00332 # FRINK: nocheck
00333 unset -- [namespace current]::state_$channel
00334 }
00335 }
00336 return $msg
00337 }
00338
00339
00340
00341
00342
00343
00344
00345 ret ::smtpd::service (type channel) {
00346 variable commands
00347 variable options
00348 upvar [namespace current]::state_$channel State
00349
00350 if {[eof $channel]} {
00351 close $channel
00352 return
00353 }
00354
00355 if {[catch {gets $channel cmdline} msg]} {
00356 close $channel
00357 Log error $msg
00358 return
00359 }
00360
00361 if { $cmdline == "" && [eof $channel] } {
00362 Log warn "client has closed the channel"
00363 return
00364 }
00365
00366 Log debug "received: $cmdline"
00367
00368 # If we are handling a DATA section, keep looking for the end of data.
00369 if {$State(indata)} {
00370 if {$cmdline == "."} {
00371 set State(indata) 0
00372 fconfigure $channel -translation crlf
00373 if {[catch {deliver $channel} err]} {
00374 # permit delivery handler to return SMTP errors in errorCode
00375 if {[regexp {\d{3}} $::errorCode]} {
00376 Puts $channel "$::errorCode $err"
00377 } else {
00378 Puts $channel "554 Transaction failed: $err"
00379 }
00380 } else {
00381 Puts $channel "250 [state $channel id]\
00382 Message accepted for delivery"
00383 }
00384 } else {
00385 # RFC 2821 section 4.5.2: Transparency
00386 if {[string match {..*} $cmdline]} {
00387 set cmdline [string range $cmdline 1 end]
00388 }
00389 lappend State(data) $cmdline
00390 }
00391 return
00392 }
00393
00394 # Process SMTP commands (case insensitive)
00395 set cmd [string toupper [lindex [split $cmdline] 0]]
00396 if {[lsearch $commands $cmd] != -1} {
00397 if {[info proc $cmd] == {}} {
00398 Puts $channel "500 $cmd not implemented"
00399 } else {
00400 # If access denied then client can only issue QUIT.
00401 if {$State(access) == "denied" && $cmd != "QUIT" } {
00402 Puts $channel "503 bad sequence of commands"
00403 } else {
00404 set r [eval $cmd $channel [list $cmdline]]
00405 }
00406 }
00407 } else {
00408 Puts $channel "500 Invalid command"
00409 }
00410
00411 return
00412 }
00413
00414
00415
00416
00417
00418 ret ::smtpd::uidchar () {
00419 set c .
00420 while {! [string is alnum $c]} {
00421 set n [expr {int(rand() * 74 + 48)}]
00422 set c [format %c $n]
00423 }
00424 return $c
00425 }
00426
00427
00428
00429
00430 ret ::smtpd::uid () {
00431 set r {}
00432 for {set cn 0} {$cn < 12} {incr cn} {
00433 append r [uidchar]
00434 }
00435 return $r
00436 }
00437
00438
00439
00440
00441
00442 ret ::smtpd::gmtoffset () {
00443 set now [clock seconds]
00444 set local [clock format $now -format "%j %H" -gmt false]
00445 set zulu [clock format $now -format "%j %H" -gmt true]
00446 set lh [expr {([scan [lindex $local 0] %d] * 24) \
00447 + [scan [lindex $local 1] %d]}]
00448 set zh [expr {([scan [lindex $zulu 0] %d] * 24) \
00449 + [scan [lindex $zulu 1] %d]}]
00450 set off [expr {$lh - $zh}]
00451 set off [format "%+03d00" $off]
00452 return $off
00453 }
00454
00455
00456
00457
00458
00459
00460 ret ::smtpd::timestamp () {
00461 set ts [clock format [clock seconds] \
00462 -format "%a, %d %b %Y %H:%M:%S" -gmt false]
00463 append ts " " [gmtoffset]
00464 return $ts
00465 }
00466
00467
00468
00469
00470
00471 ret ::smtpd::server_ip () {
00472 set me [socket -server xxx -myaddr [info hostname] 0]
00473 set ip [lindex [fconfigure $me -sockname] 0]
00474 close $me
00475 return $ip
00476 }
00477
00478
00479
00480
00481
00482
00483
00484
00485
00486 ret ::smtpd::deliver (type channel) {
00487 set deliverMIME [cget deliverMIME]
00488 if { $deliverMIME != {} \
00489 && [state $channel from] != {} \
00490 && [state $channel to] != {} \
00491 && [state $channel data] != {} } {
00492
00493 # create a MIME token from the mail message.
00494 set tok [mime::initialize -string \
00495 [join [state $channel data] "\n"]]
00496 # mime::setheader $tok "From" [state $channel from]
00497 # foreach recipient [state $channel to] {
00498 # mime::setheader $tok "To" $recipient -mode append
00499 # }
00500
00501 # catch and rethrow any errors.
00502 set err [catch {eval $deliverMIME [list $tok]} msg]
00503 mime::finalize $tok -subordinates all
00504 if {$err} {
00505 Log debug "error in deliver: $msg"
00506 return -code error -errorcode $::errorCode \
00507 -errorinfo $::errorInfo $msg
00508 }
00509
00510 } else {
00511 # Try the old interface
00512 deliver_old $channel
00513 }
00514 }
00515
00516
00517
00518
00519
00520
00521
00522 ret ::smtpd::deliver_old (type channel) {
00523 set deliver [cget deliver]
00524 if { $deliver != {} \
00525 && [state $channel from] != {} \
00526 && [state $channel to] != {} \
00527 && [state $channel data] != {} } {
00528 if {[catch {$deliver [state $channel from] \
00529 [state $channel to] \
00530 [state $channel data]} msg]} {
00531 Log debug "error in deliver: $msg"
00532 return -code error -errorcode $::errorCode \
00533 -errorinfo $::errorInfo $msg
00534 }
00535 }
00536 }
00537
00538
00539 ret ::smtpd::split_address (type address) {
00540 set start [string first < $address]
00541 set end [string last > $address]
00542 set addr [string range $address $start $end]
00543 incr end
00544 set opts [string trim [string range $address $end end]]
00545 return [list $addr $opts]
00546 }
00547
00548
00549
00550
00551
00552
00553
00554
00555
00556 ret ::smtpd::HELO (type channel , type line) {
00557 variable options
00558
00559 if {[state $channel domain] != {}} {
00560 Puts $channel "503 bad sequence of commands"
00561 Log debug "HELO received out of sequence."
00562 return
00563 }
00564
00565 set r [regexp -nocase {^HELO\s+([-\w\.]+)\s*$} $line -> domain]
00566 if {$r == 0} {
00567 Puts $channel "501 Syntax error in parameters or arguments"
00568 Log debug "HELO received \"$line\""
00569 return
00570 }
00571 Puts $channel "250 $options(serveraddr) Hello $domain\
00572 \[[state $channel client_addr]\], pleased to meet you"
00573 state $channel domain $domain
00574 Log debug "HELO on $channel from $domain"
00575 return
00576 }
00577
00578
00579
00580
00581
00582
00583 ret ::smtpd::EHLO (type channel , type line) {
00584 variable options
00585 variable extensions
00586
00587 if {[state $channel domain] != {}} {
00588 Puts $channel "503 bad sequence of commands"
00589 Log debug "EHLO received out of sequence."
00590 return
00591 }
00592
00593 set r [regexp -nocase {^EHLO\s+([-\w\.]+)\s*$} $line -> domain]
00594 if {$r == 0} {
00595 Puts $channel "501 Syntax error in parameters or arguments"
00596 Log debug "EHLO received \"$line\""
00597 return
00598 }
00599 Puts $channel "250-$options(serveraddr) Hello $domain\
00600 \[[state $channel client_addr]\], pleased to meet you"
00601 foreach {extn opts} [array get extensions] {
00602 Puts $channel [string trimright "250-$extn $opts"]
00603 }
00604 Puts $channel "250 Ready for mail."
00605 state $channel domain $domain
00606 Log debug "EHLO on $channel from $domain"
00607 return
00608 }
00609
00610
00611
00612
00613
00614
00615 ret ::smtpd::MAIL (type channel , type line) {
00616 set r [regexp -nocase {^MAIL FROM:\s*(.*)} $line -> from]
00617 if {$r == 0} {
00618 Puts $channel "501 Syntax error in parameters or arguments"
00619 Log debug "MAIL received \"$line\""
00620 return
00621 }
00622 if {[catch {
00623 set from [split_address $from]
00624 set opts [lindex $from 1]
00625 set from [lindex $from 0]
00626 eval array set addr [mime::parseaddress $from]
00627 # RFC2821 3.7: we must accept null return path addresses.
00628 if {[string equal "<>" $from]} {
00629 set addr(error) {}
00630 }
00631 } msg]} {
00632 set addr(error) $msg
00633 }
00634 if {$addr(error) != {} } {
00635 Log debug "MAIL failed $addr(error)"
00636 Puts $channel "501 Syntax error in parameters or arguments"
00637 return
00638 }
00639
00640 if {[cget -validate_sender] != {}} {
00641 if {[catch {eval [cget -validate_sender] $addr(address)}]} {
00642 # this user has been denied
00643 Log info "MAIL denied user $addr(address)"
00644 Puts $channel "553 Requested action not taken:\
00645 mailbox name not allowed"
00646 return
00647 }
00648 }
00649
00650 Log debug "MAIL FROM: $addr(address)"
00651 state $channel from $from
00652 state $channel options $opts
00653 Puts $channel "250 OK"
00654 return
00655 }
00656
00657
00658
00659
00660
00661
00662
00663
00664
00665
00666
00667
00668 ret ::smtpd::RCPT (type channel , type line) {
00669 set r [regexp -nocase {^RCPT TO:\s*(.*)} $line -> to]
00670 if {$r == 0} {
00671 Puts $channel "501 Syntax error in parameters or arguments"
00672 Log debug "RCPT received \"$line\""
00673 return
00674 }
00675 if {[catch {
00676 set to [split_address $to]
00677 set opts [lindex $to 1]
00678 set to [lindex $to 0]
00679 eval array set addr [mime::parseaddress $to]
00680 } msg]} {
00681 set addr(error) $msg
00682 }
00683
00684 if {$addr(error) != {}} {
00685 Log debug "RCPT failed $addr(error)"
00686 Puts $channel "501 Syntax error in parameters or arguments"
00687 return
00688 }
00689
00690 if {[string match -nocase "postmaster" $addr(local)]} {
00691 # we MUST support this recipient somehow as mail.
00692 Log notice "RCPT to postmaster"
00693 } else {
00694 if {[cget -validate_recipient] != {}} {
00695 if {[catch {eval [cget -validate_recipient] $addr(address)}]} {
00696 # this recipient has been denied
00697 Log info "RCPT denied mailbox $addr(address)"
00698 Puts $channel "553 Requested action not taken:\
00699 mailbox name not allowed"
00700 return
00701 }
00702 }
00703 }
00704
00705 Log debug "RCPT TO: $addr(address)"
00706 set recipients {}
00707 catch {set recipients [state $channel to]}
00708 lappend recipients $to
00709 state $channel to $recipients
00710 Puts $channel "250 OK"
00711 return
00712 }
00713
00714
00715
00716
00717
00718
00719
00720
00721
00722
00723
00724
00725
00726
00727
00728
00729
00730 ret ::smtpd::DATA (type channel , type line) {
00731 variable version
00732 upvar [namespace current]::state_$channel State
00733 Log debug "DATA"
00734 if { $State(from) == {}} {
00735 Puts $channel "503 bad sequence: no sender specified"
00736 } elseif { $State(to) == {}} {
00737 Puts $channel "503 bad sequence: no recipient specified"
00738 } else {
00739 Puts $channel "354 Enter mail, end with \".\" on a line by itself"
00740 set State(id) [uid]
00741 set State(indata) 1
00742
00743 lappend trace "Return-Path: $State(from)"
00744 lappend trace "Received: from [state $channel domain]\
00745 \[[state $channel client_addr]\]"
00746 lappend trace "\tby [info hostname] with tcllib smtpd ($version)"
00747 if {[info exists State(tls)] && $State(tls)} {
00748 catch {
00749 array set t [::tls::status $channel]
00750 lappend trace "\t(version=TLS1/SSL3 cipher=$t(cipher) bits=$t(sbits) verify=NO)"
00751 }
00752 }
00753 lappend trace "\tid $State(id); [timestamp]"
00754 set State(data) $trace
00755 fconfigure $channel -translation auto ;# naughty: RFC2821:2.3.7
00756 }
00757 return
00758 }
00759
00760
00761
00762
00763
00764
00765
00766 ret ::smtpd::RSET (type channel , type line) {
00767 upvar [namespace current]::state_$channel State
00768 Log debug "RSET on $channel"
00769 if {[catch {initializeState $channel} msg]} {
00770 Log warn "RSET: $msg"
00771 }
00772 Puts $channel "250 OK"
00773 return
00774 }
00775
00776
00777
00778
00779
00780
00781
00782
00783
00784
00785
00786
00787
00788
00789
00790
00791
00792
00793
00794
00795
00796
00797
00798
00799
00800
00801
00802 ret ::smtpd::HELP (type channel , type line) {
00803 variable Help
00804 set cmd {}
00805 regexp {^HELP\s*(\w+)?} $line -> cmd
00806 if {[info exists Help($cmd)]} {
00807 foreach line $Help($cmd) {
00808 Puts $channel "214-$line"
00809 }
00810 Puts $channel "214 End of HELP"
00811 } else {
00812 Puts $channel "504 HELP topic \"$cmd\" unknown."
00813 }
00814 }
00815
00816
00817
00818
00819
00820
00821
00822 ret ::smtpd::NOOP (type channel , type line) {
00823 set str {}
00824 regexp -nocase {^NOOP (.*)$} -> str
00825 Log debug "NOOP: $str"
00826 Puts $channel "250 OK"
00827 return
00828 }
00829
00830
00831
00832
00833
00834
00835
00836
00837
00838
00839 ret ::smtpd::QUIT (type channel , type line) {
00840 variable options
00841 upvar [namespace current]::state_$channel State
00842
00843 Log debug "QUIT on $channel"
00844 Puts $channel "221 $options(serveraddr) Service closing transmission channel"
00845 close $channel
00846
00847 # cleanup the session state array.
00848 unset State
00849 return
00850 }
00851
00852
00853
00854
00855
00856
00857
00858
00859 ret ::smtpd::STARTTLS (type channel , type line) {
00860 variable options
00861 upvar [namespace current]::state_$channel State
00862
00863 Log debug "$line on $channel"
00864 if {![string equal $line STARTTLS]} {
00865 Puts $channel "501 Syntax error (no parameters allowed)"
00866 return
00867 }
00868
00869 if {[lsearch -exact $options(tlsopts) -certfile] == -1
00870 || [lsearch -exact $options(tlsopts) -keyfile] == -1} {
00871 Puts $channel "454 TLS not available due to temporary reason"
00872 return
00873 }
00874
00875 set import [linsert $options(tlsopts) 0 ::tls::import $channel -server 1]
00876 Puts $channel "220 Ready to start TLS"
00877 if {[catch $import msg]} {
00878 Puts $channel "454 TLS not available due to temporary reason"
00879 } else {
00880 set State(domain) {}; # RFC3207:4.2
00881 set State(tls) 1
00882 }
00883 return
00884 }
00885
00886
00887
00888
00889
00890 ret ::smtpd::tlscallback (type option , type args) {
00891 switch -exact -- $option {
00892 "error" {
00893 foreach {chan msg} $args break
00894 Log error "TLS error '$msg'"
00895 }
00896 "verify" {
00897 foreach {chan depth cert rc err} $args break
00898 if {$rc ne "1"} {
00899 Log error "TLS verify/$depth Bad cert '$err' (rc=$rc)"
00900 } else {
00901 array set c $cert
00902 Log notice "TLS verify/$depth: $c(subject)"
00903 }
00904 return $rc
00905 }
00906 "info" {
00907 foreach {chan major minor state msg} $args break
00908 if {$msg ne ""} { append state ": $msg" }
00909 Log debug "TLS ${major}.${minor} $state"
00910 }
00911 default {
00912 Log warn "bad option \"$option\" in smtpd::callback"
00913 }
00914 }
00915 }
00916
00917
00918
00919 package provide smtpd $smtpd::version
00920
00921
00922
00923
00924
00925
00926