smtp.tcl

Go to the documentation of this file.
00001 /*  smtp.tcl - SMTP client*/
00002 /* */
00003 /*  Copyright (c) 1999-2000 Marshall T. Rose*/
00004 /*  Copyright (c) 2003-2006 Pat Thoyts*/
00005 /* */
00006 /*  See the file "license.terms" for information on usage and redistribution*/
00007 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00008 /* */
00009 
00010 package require Tcl 8.3
00011 package require mime 1.4.1
00012 
00013 catch {
00014     package require SASL 1.0;           /*  tcllib 1.8*/
00015     package require SASL::NTLM 1.0;     /*  tcllib 1.8*/
00016 }
00017 
00018 /* */
00019 /*  state variables:*/
00020 /* */
00021 /*     sd: socket to server*/
00022 /*     afterID: afterID associated with ::smtp::timer*/
00023 /*     options: array of user-supplied options*/
00024 /*     readable: semaphore for vwait*/
00025 /*     addrs: number of recipients negotiated*/
00026 /*     error: error during read*/
00027 /*     line: response read from server*/
00028 /*     crP: just put a \r in the data*/
00029 /*     nlP: just put a \n in the data*/
00030 /*     size: number of octets sent in DATA*/
00031 /* */
00032 
00033 
00034 namespace ::smtp {
00035     variable version 1.4.4
00036     variable trf 1
00037     variable smtp
00038     array  smtp =  { uid 0 }
00039 
00040     namespace export sendmessage
00041 }
00042 
00043 if {[catch {package require Trf  2.0}]} {
00044     /*  Trf is not available, but we can live without it as long as the*/
00045     /*  transform and unstack procs are defined.*/
00046 
00047     /*  Warning!*/
00048     /*  This is a fragile emulation of the more general calling sequence*/
00049     /*  that appears to work with this code here.*/
00050 
00051     ret  transform (type args) {
00052     upvar state mystate
00053     set mystate(size) 1
00054     }
00055     ret  unstack (type channel) {
00056         # do nothing
00057         return
00058     }
00059      ::smtp = ::trf 0
00060 }
00061 
00062 
00063 /*  ::smtp::sendmessage --*/
00064 /* */
00065 /*  Sends a mime object (containing a message) to some recipients*/
00066 /* */
00067 /*  Arguments:*/
00068 /*  part  The MIME object containing the message to send*/
00069 /*        args  A list of arguments specifying various options for sending the*/
00070 /*              message:*/
00071 /*              -atleastone  A boolean specifying whether or not to send the*/
00072 /*                           message at all if any of the recipients are */
00073 /*                           invalid.  A value of false (as defined by */
00074 /*                           ::smtp::boolean) means that ALL recipients must be*/
00075 /*                           valid in order to send the message.  A value of*/
00076 /*                           true means that as long as at least one recipient*/
00077 /*                           is valid, the message will be sent.*/
00078 /*              -debug       A boolean specifying whether or not debugging is*/
00079 /*                           on.  If debugging is enabled, status messages are */
00080 /*                           printed to stderr while trying to send mail.*/
00081 /*              -queue       A boolean specifying whether or not the message*/
00082 /*                           being sent should be queued for later delivery.*/
00083 /*              -header      A single RFC 822 header key and value (as a list),*/
00084 /*                           used to specify to whom to send the message */
00085 /*                           (To, Cc, Bcc), the "From", etc.*/
00086 /*              -originator  The originator of the message (equivalent to*/
00087 /*                           specifying a From header).*/
00088 /*              -recipients  A string containing recipient e-mail addresses.*/
00089 /*                           NOTE: This option overrides any recipient addresses*/
00090 /*                           specified with -header.*/
00091 /*              -servers     A list of mail servers that could process the*/
00092 /*                           request.*/
00093 /*              -ports       A list of SMTP ports to use for each SMTP server*/
00094 /*                           specified*/
00095 /*              -client      The string to use as our host name for EHLO or HELO*/
00096 /*                           This defaults to 'localhost' or [info hostname]*/
00097 /*              -maxsecs     Maximum number of seconds to allow the SMTP server*/
00098 /*                           to accept the message. If not specified, the default*/
00099 /*                           is 120 seconds.*/
00100 /*              -usetls      A boolean flag. If the server supports it and we*/
00101 /*                           have the package, use TLS to secure the connection.*/
00102 /*              -tlspolicy   A command to call if the TLS negotiation fails for*/
00103 /*                           some reason. Return 'insecure' to continue with*/
00104 /*                           normal SMTP or 'secure' to close the connection and*/
00105 /*                           try another server.*/
00106 /*              -username    These are needed if your SMTP server requires*/
00107 /*              -password    authentication.*/
00108 /* */
00109 /*  Results:*/
00110 /*  Message is sent.  On success, return "".  On failure, throw an*/
00111 /*        exception with an error code and error message.*/
00112 
00113 ret  ::smtp::sendmessage (type part , type args) {
00114     global errorCode errorInfo
00115 
00116     # Here are the meanings of the following boolean variables:
00117     # aloP -- value of -atleastone option above.
00118     # debugP -- value of -debug option above.
00119     # origP -- 1 if -originator option was specified, 0 otherwise.
00120     # queueP -- value of -queue option above.
00121 
00122     set aloP 0
00123     set debugP 0
00124     set origP 0
00125     set queueP 0
00126     set maxsecs 120
00127     set originator ""
00128     set recipients ""
00129     set servers [list localhost]
00130     set client "" ;# default is set after options processing
00131     set ports [list 25]
00132     set tlsP 1
00133     set tlspolicy {}
00134     set username {}
00135     set password {}
00136 
00137     array set header ""
00138 
00139     # lowerL will contain the list of header keys (converted to lower case) 
00140     # specified with various -header options.  mixedL is the mixed-case version
00141     # of the list.
00142     set lowerL ""
00143     set mixedL ""
00144 
00145     # Parse options (args).
00146 
00147     if {[expr {[llength $args]%2}]} {
00148         # Some option didn't get a value.
00149         error "Each option must have a value!  Invalid option list: $args"
00150     }
00151     
00152     foreach {option value} $args {
00153         switch -- $option {
00154             -atleastone {set aloP   [boolean $value]}
00155             -debug      {set debugP [boolean $value]}
00156             -queue      {set queueP [boolean $value]}
00157             -usetls     {set tlsP   [boolean $value]}
00158             -tlspolicy  {set tlspolicy $value}
00159         -maxsecs    {set maxsecs [expr {$value < 0 ? 0 : $value}]}
00160             -header {
00161                 if {[llength $value] != 2} {
00162                     error "-header expects a key and a value, not $value"
00163                 }
00164                 set mixed [lindex $value 0]
00165                 set lower [string tolower $mixed]
00166                 set disallowedHdrList \
00167                     [list content-type \
00168                           content-transfer-encoding \
00169                           content-md5 \
00170                           mime-version]
00171                 if {[lsearch -exact $disallowedHdrList $lower] > -1} {
00172                     error "Content-Type, Content-Transfer-Encoding,\
00173                         Content-MD5, and MIME-Version cannot be user-specified."
00174                 }
00175                 if {[lsearch -exact $lowerL $lower] < 0} {
00176                     lappend lowerL $lower
00177                     lappend mixedL $mixed
00178                 }               
00179 
00180                 lappend header($lower) [lindex $value 1]
00181             }
00182 
00183             -originator {
00184                 set originator $value
00185                 if {$originator == ""} {
00186                     set origP 1
00187                 }
00188             }
00189 
00190             -recipients {
00191                 set recipients $value
00192             }
00193 
00194             -servers {
00195                 set servers $value
00196             }
00197 
00198             -client {
00199                 set client $value
00200             }
00201 
00202             -ports {
00203                 set ports $value
00204             }
00205 
00206             -username { set username $value }
00207             -password { set password $value }
00208 
00209             default {
00210                 error "unknown option $option"
00211             }
00212         }
00213     }
00214 
00215     if {[lsearch -glob $lowerL resent-*] >= 0} {
00216         set prefixL resent-
00217         set prefixM Resent-
00218     } else {
00219         set prefixL ""
00220         set prefixM ""
00221     }
00222 
00223     # Set a bunch of variables whose value will be the real header to be used
00224     # in the outbound message (with proper case and prefix).
00225 
00226     foreach mixed {From Sender To cc Dcc Bcc Date Message-ID} {
00227         set lower [string tolower $mixed]
00228     # FRINK: nocheck
00229         set ${lower}L $prefixL$lower
00230     # FRINK: nocheck
00231         set ${lower}M $prefixM$mixed
00232     }
00233 
00234     if {$origP} {
00235         # -originator was specified with "", so SMTP sender should be marked "".
00236         set sender ""
00237     } else {
00238         # -originator was specified with a value, OR -originator wasn't
00239         # specified at all.
00240         
00241         # If no -originator was provided, get the originator from the "From"
00242         # header.  If there was no "From" header get it from the username
00243         # executing the script.
00244 
00245         set who "-originator"
00246         if {$originator == ""} {
00247             if {![info exists header($fromL)]} {
00248                 set originator $::tcl_platform(user)
00249             } else {
00250                 set originator [join $header($fromL) ,]
00251 
00252                 # Indicate that we're using the From header for the originator.
00253 
00254                 set who $fromM
00255             }
00256         }
00257         
00258     # If there's no "From" header, create a From header with the value
00259     # of -originator as the value.
00260 
00261         if {[lsearch -exact $lowerL $fromL] < 0} {
00262             lappend lowerL $fromL
00263             lappend mixedL $fromM
00264             lappend header($fromL) $originator
00265         }
00266 
00267     # ::mime::parseaddress returns a list whose elements are huge key-value
00268     # lists with info about the addresses.  In this case, we only want one
00269     # originator, so we want the length of the main list to be 1.
00270 
00271         set addrs [::mime::parseaddress $originator]
00272         if {[llength $addrs] > 1} {
00273             error "too many mailboxes in $who: $originator"
00274         }
00275         array set aprops {error "invalid address \"$from\""}
00276         array set aprops [lindex $addrs 0]
00277         if {$aprops(error) != ""} {
00278             error "error in $who: $aprops(error)"
00279         }
00280 
00281     # sender = validated originator or the value of the From header.
00282 
00283         set sender $aprops(address)
00284 
00285     # If no Sender header has been specified and From is different from
00286     # originator, then set the sender header to the From.  Otherwise, don't
00287     # specify a Sender header.
00288         set from [join $header($fromL) ,]
00289         if {[lsearch -exact $lowerL $senderL] < 0 && \
00290                 [string compare $originator $from]} {
00291             if {[info exists aprops]} {
00292                 unset aprops
00293             }
00294             array set aprops {error "invalid address \"$from\""}
00295             array set aprops [lindex [::mime::parseaddress $from] 0]
00296             if {$aprops(error) != ""} {
00297                 error "error in $fromM: $aprops(error)"
00298             }
00299             if {[string compare $aprops(address) $sender]} {
00300                 lappend lowerL $senderL
00301                 lappend mixedL $senderM
00302                 lappend header($senderL) $aprops(address)
00303             }
00304         }
00305     }
00306 
00307     # We're done parsing the arguments.
00308 
00309     if {$recipients != ""} {
00310         set who -recipients
00311     } elseif {![info exists header($toL)]} {
00312         error "need -header \"$toM ...\""
00313     } else {
00314         set recipients [join $header($toL) ,]
00315     # Add Cc values to recipients list
00316     set who $toM
00317         if {[info exists header($ccL)]} {
00318             append recipients ,[join $header($ccL) ,]
00319             append who /$ccM
00320         }
00321 
00322         set dccInd [lsearch -exact $lowerL $dccL]
00323         if {$dccInd >= 0} {
00324         # Add Dcc values to recipients list, and get rid of Dcc header
00325         # since we don't want to output that.
00326             append recipients ,[join $header($dccL) ,]
00327             append who /$dccM
00328 
00329             unset header($dccL)
00330             set lowerL [lreplace $lowerL $dccInd $dccInd]
00331             set mixedL [lreplace $mixedL $dccInd $dccInd]
00332         }
00333     }
00334 
00335     set brecipients ""
00336     set bccInd [lsearch -exact $lowerL $bccL]
00337     if {$bccInd >= 0} {
00338         set bccP 1
00339 
00340     # Build valid bcc list and remove bcc element of header array (so that
00341     # bcc info won't be sent with mail).
00342         foreach addr [::mime::parseaddress [join $header($bccL) ,]] {
00343             if {[info exists aprops]} {
00344                 unset aprops
00345             }
00346             array set aprops {error "invalid address \"$from\""}
00347             array set aprops $addr
00348             if {$aprops(error) != ""} {
00349                 error "error in $bccM: $aprops(error)"
00350             }
00351             lappend brecipients $aprops(address)
00352         }
00353 
00354         unset header($bccL)
00355         set lowerL [lreplace $lowerL $bccInd $bccInd]
00356         set mixedL [lreplace $mixedL $bccInd $bccInd]
00357     } else {
00358         set bccP 0
00359     }
00360 
00361     # If there are no To headers, add "" to bcc list.  WHY??
00362     if {[lsearch -exact $lowerL $toL] < 0} {
00363         lappend lowerL $bccL
00364         lappend mixedL $bccM
00365         lappend header($bccL) ""
00366     }
00367 
00368     # Construct valid recipients list from recipients list.
00369 
00370     set vrecipients ""
00371     foreach addr [::mime::parseaddress $recipients] {
00372         if {[info exists aprops]} {
00373             unset aprops
00374         }
00375         array set aprops {error "invalid address \"$from\""}
00376         array set aprops $addr
00377         if {$aprops(error) != ""} {
00378             error "error in $who: $aprops(error)"
00379         }
00380         lappend vrecipients $aprops(address)
00381     }
00382 
00383     # If there's no date header, get the date from the mime message.  Same for
00384     # the message-id.
00385 
00386     if {([lsearch -exact $lowerL $dateL] < 0) \
00387             && ([catch { ::mime::getheader $part $dateL }])} {
00388         lappend lowerL $dateL
00389         lappend mixedL $dateM
00390         lappend header($dateL) [::mime::parsedatetime -now proper]
00391     }
00392 
00393     if {([lsearch -exact $lowerL ${message-idL}] < 0) \
00394             && ([catch { ::mime::getheader $part ${message-idL} }])} {
00395         lappend lowerL ${message-idL}
00396         lappend mixedL ${message-idM}
00397         lappend header(${message-idL}) [::mime::uniqueID]
00398 
00399     }
00400 
00401     # Get all the headers from the MIME object and save them so that they can
00402     # later be restored.
00403     set savedH [::mime::getheader $part]
00404 
00405     # Take all the headers defined earlier and add them to the MIME message.
00406     foreach lower $lowerL mixed $mixedL {
00407         foreach value $header($lower) {
00408             ::mime::setheader $part $mixed $value -mode append
00409         }
00410     }
00411 
00412     if {[string length $client] < 1} {
00413         if {![string compare $servers localhost]} {
00414             set client localhost
00415         } else {
00416             set client [info hostname]
00417         }
00418     }
00419 
00420     # Create smtp token, which essentially means begin talking to the SMTP
00421     # server.
00422     set token [initialize -debug $debugP -client $client \
00423                         -maxsecs $maxsecs -usetls $tlsP \
00424                                 -multiple $bccP -queue $queueP \
00425                                 -servers $servers -ports $ports \
00426                                 -tlspolicy $tlspolicy \
00427                                 -username $username -password $password]
00428 
00429     if {![string match "::smtp::*" $token]} {
00430     # An error occurred and $token contains the error info
00431     array set respArr $token
00432     return -code error $respArr(diagnostic)
00433     }
00434 
00435     set code [catch { sendmessageaux $token $part \
00436                                            $sender $vrecipients $aloP } \
00437                     result]
00438     set ecode $errorCode
00439     set einfo $errorInfo
00440 
00441     # Send the message to bcc recipients as a MIME attachment.
00442 
00443     if {($code == 0) && ($bccP)} {
00444         set inner [::mime::initialize -canonical message/rfc822 \
00445                                     -header [list Content-Description \
00446                                                   "Original Message"] \
00447                                     -parts [list $part]]
00448 
00449         set subject "\[$bccM\]"
00450         if {[info exists header(subject)]} {
00451             append subject " " [lindex $header(subject) 0] 
00452         }
00453 
00454         set outer [::mime::initialize \
00455                          -canonical multipart/digest \
00456                          -header [list From $originator] \
00457                          -header [list Bcc ""] \
00458                          -header [list Date \
00459                                        [::mime::parsedatetime -now proper]] \
00460                          -header [list Subject $subject] \
00461                          -header [list Message-ID [::mime::uniqueID]] \
00462                          -header [list Content-Description \
00463                                        "Blind Carbon Copy"] \
00464                          -parts [list $inner]]
00465 
00466 
00467         set code [catch { sendmessageaux $token $outer \
00468                                                $sender $brecipients \
00469                                                $aloP } result2]
00470         set ecode $errorCode
00471         set einfo $errorInfo
00472 
00473         if {$code == 0} {
00474             set result [concat $result $result2]
00475         } else {
00476             set result $result2
00477         }
00478 
00479         catch { ::mime::finalize $inner -subordinates none }
00480         catch { ::mime::finalize $outer -subordinates none }
00481     }
00482 
00483     # Determine if there was any error in prior operations and set errorcodes
00484     # and error messages appropriately.
00485     
00486     switch -- $code {
00487         0 {
00488             set status orderly
00489         }
00490 
00491         7 {
00492             set code 1
00493             array set response $result
00494             set result "$response(code): $response(diagnostic)"
00495             set status abort
00496         }
00497 
00498         default {
00499             set status abort
00500         }
00501     }
00502 
00503     # Destroy SMTP token 'cause we're done with it.
00504     
00505     catch { finalize $token -close $status }
00506 
00507     # Restore provided MIME object to original state (without the SMTP headers).
00508     
00509     foreach key [::mime::getheader $part -names] {
00510         mime::setheader $part $key "" -mode delete
00511     }
00512     foreach {key values} $savedH {
00513         foreach value $values {
00514             ::mime::setheader $part $key $value -mode append
00515         }
00516     }
00517 
00518     return -code $code -errorinfo $einfo -errorcode $ecode $result
00519 }
00520 
00521 /*  ::smtp::sendmessageaux --*/
00522 /* */
00523 /*  Sends a mime object (containing a message) to some recipients using an*/
00524 /*        existing SMTP token.*/
00525 /* */
00526 /*  Arguments:*/
00527 /*        token       SMTP token that has an open connection to the SMTP server.*/
00528 /*  part        The MIME object containing the message to send.*/
00529 /*        originator  The e-mail address of the entity sending the message,*/
00530 /*                    usually the From clause.*/
00531 /*        recipients  List of e-mail addresses to whom message will be sent.*/
00532 /*        aloP        Boolean "atleastone" setting; see the -atleastone option*/
00533 /*                    in ::smtp::sendmessage for details.*/
00534 /* */
00535 /*  Results:*/
00536 /*  Message is sent.  On success, return "".  On failure, throw an*/
00537 /*        exception with an error code and error message.*/
00538 
00539 ret  ::smtp::sendmessageaux (type token , type part , type originator , type recipients , type aloP) {
00540     global errorCode errorInfo
00541 
00542     winit $token $part $originator
00543 
00544     set goodP 0
00545     set badP 0
00546     set oops ""
00547     foreach recipient $recipients {
00548         set code [catch { waddr $token $recipient } result]
00549         set ecode $errorCode
00550         set einfo $errorInfo
00551 
00552         switch -- $code {
00553             0 {
00554                 incr goodP
00555             }
00556 
00557             7 {
00558                 incr badP
00559 
00560                 array set response $result
00561                 lappend oops [list $recipient $response(code) \
00562                                    $response(diagnostic)]
00563             }
00564 
00565             default {
00566                 return -code $code -errorinfo $einfo -errorcode $ecode $result
00567             }
00568         }
00569     }
00570 
00571     if {($goodP) && ((!$badP) || ($aloP))} {
00572         wtext $token $part
00573     } else {
00574         catch { talk $token 300 RSET }
00575     }
00576 
00577     return $oops
00578 }
00579 
00580 /*  ::smtp::initialize --*/
00581 /* */
00582 /*  Create an SMTP token and open a connection to the SMTP server.*/
00583 /* */
00584 /*  Arguments:*/
00585 /*        args  A list of arguments specifying various options for sending the*/
00586 /*              message:*/
00587 /*              -debug       A boolean specifying whether or not debugging is*/
00588 /*                           on.  If debugging is enabled, status messages are */
00589 /*                           printed to stderr while trying to send mail.*/
00590 /*              -client      Either localhost or the name of the local host.*/
00591 /*              -multiple    Multiple messages will be sent using this token.*/
00592 /*              -queue       A boolean specifying whether or not the message*/
00593 /*                           being sent should be queued for later delivery.*/
00594 /*              -servers     A list of mail servers that could process the*/
00595 /*                           request.*/
00596 /*              -ports       A list of ports on mail servers that could process*/
00597 /*                           the request (one port per server-- defaults to 25).*/
00598 /*              -usetls      A boolean to indicate we will use TLS if possible.*/
00599 /*              -tlspolicy   Command called if TLS setup fails.*/
00600 /*              -username    These provide the authentication information */
00601 /*              -password    to be used if needed by the SMTP server.*/
00602 /* */
00603 /*  Results:*/
00604 /*  On success, return an smtp token.  On failure, throw*/
00605 /*        an exception with an error code and error message.*/
00606 
00607 ret  ::smtp::initialize (type args) {
00608     global errorCode errorInfo
00609 
00610     variable smtp
00611 
00612     set token [namespace current]::[incr smtp(uid)]
00613     # FRINK: nocheck
00614     variable $token
00615     upvar 0 $token state
00616 
00617     array set state [list afterID "" options "" readable 0]
00618     array set options [list -debug 0 -client localhost -multiple 1 \
00619                             -maxsecs 120 -queue 0 -servers localhost \
00620                             -ports 25 -usetls 1 -tlspolicy {} \
00621                             -username {} -password {}]
00622     array set options $args
00623     set state(options) [array get options]
00624 
00625     # Iterate through servers until one accepts a connection (and responds
00626     # nicely).
00627    
00628     set index 0 
00629     foreach server $options(-servers) {
00630     set state(readable) 0
00631         if {[llength $options(-ports)] >= $index} {
00632             set port [lindex $options(-ports) $index]
00633         } else {
00634             set port 25
00635         }
00636         if {$options(-debug)} {
00637             puts stderr "Trying $server..."
00638             flush stderr
00639         }
00640 
00641         if {[info exists state(sd)]} {
00642             unset state(sd)
00643         }
00644 
00645         if {[set code [catch {
00646             set state(sd) [socket -async $server $port]
00647             fconfigure $state(sd) -blocking off -translation binary
00648             fileevent $state(sd) readable [list ::smtp::readable $token]
00649         } result]]} {
00650             set ecode $errorCode
00651             set einfo $errorInfo
00652 
00653             catch { close $state(sd) }
00654             continue
00655         }
00656 
00657         if {[set code [catch { hear $token 600 } result]]} {
00658             array set response [list code 400 diagnostic $result]
00659         } else {
00660             array set response $result
00661         }
00662         set ecode $errorCode
00663         set einfo $errorInfo
00664         switch -- $response(code) {
00665             220 {
00666             }
00667 
00668             421 - default {
00669                 # 421 - Temporary problem on server
00670                 catch {close $state(sd)}
00671                 continue
00672             }
00673         }
00674 
00675         set r [initialize_ehlo $token]
00676         if {$r != {}} {
00677             return $r
00678         }
00679         incr index
00680     }
00681 
00682     # None of the servers accepted our connection, so close everything up and
00683     # return an error.
00684     finalize $token -close drop
00685 
00686     return -code $code -errorinfo $einfo -errorcode $ecode $result
00687 }
00688 
00689 ret  ::smtp::initialize_ehlo (type token) {
00690     global errorCode errorInfo
00691     upvar einfo einfo
00692     upvar ecode ecode
00693     upvar code  code
00694     
00695     # FRINK: nocheck
00696     variable $token
00697     upvar 0 $token state
00698     array set options $state(options)
00699 
00700     # Try enhanced SMTP first.
00701 
00702     if {[set code [catch {smtp::talk $token 300 "EHLO $options(-client)"} \
00703                        result]]} {
00704         array set response [list code 400 diagnostic $result args ""]
00705     } else {
00706         array set response $result
00707     }
00708     set ecode $errorCode
00709     set einfo $errorInfo
00710     if {(500 <= $response(code)) && ($response(code) <= 599)} {
00711         if {[set code [catch { talk $token 300 \
00712                                    "HELO $options(-client)" } \
00713                            result]]} {
00714             array set response [list code 400 diagnostic $result args ""]
00715         } else {
00716             array set response $result
00717         }
00718         set ecode $errorCode
00719         set einfo $errorInfo
00720     }
00721     
00722     if {$response(code) == 250} {
00723         # Successful response to HELO or EHLO command, so set up queuing
00724         # and whatnot and return the token.
00725         
00726         set state(esmtp) $response(args)
00727 
00728         if {(!$options(-multiple)) \
00729                 && ([lsearch $response(args) ONEX] >= 0)} {
00730             catch {smtp::talk $token 300 ONEX}
00731         }
00732         if {($options(-queue)) \
00733                 && ([lsearch $response(args) XQUE] >= 0)} {
00734             catch {smtp::talk $token 300 QUED}
00735         }
00736         
00737         # Support STARTTLS extension.
00738         # The state(tls) item is used to see if we have already tried this.
00739         if {($options(-usetls)) && ![info exists state(tls)] \
00740                 && (([lsearch $response(args) STARTTLS] >= 0)
00741                     || ([lsearch $response(args) TLS] >= 0))} {
00742             if {![catch {package require tls}]} {
00743                 set state(tls) 0
00744                 if {![catch {smtp::talk $token 300 STARTTLS} resp]} {
00745                     array set starttls $resp
00746                     if {$starttls(code) == 220} {
00747                         fileevent $state(sd) readable {}
00748                         catch {
00749                             ::tls::import $state(sd)
00750                             catch {::tls::handshake $state(sd)} msg
00751                             set state(tls) 1
00752                         } 
00753                         fileevent $state(sd) readable \
00754                             [list ::smtp::readable $token]
00755                         return [initialize_ehlo $token]
00756                     } else {
00757                         # Call a TLS client policy proc here
00758                         #  returns secure close and try another server.
00759                         #  returns insecure continue on current socket
00760                         set policy insecure
00761                         if {$options(-tlspolicy) != {}} {
00762                             catch {
00763                                 eval $options(-tlspolicy) \
00764                                     [list $starttls(code)] \
00765                                     [list $starttls(diagnostic)]
00766                             } policy
00767                         }
00768                         if {$policy != "insecure"} {
00769                             set code error
00770                             set ecode $starttls(code)
00771                             set einfo $starttls(diagnostic)
00772                             catch {close $state(sd)}
00773                             return {}
00774                         }
00775                     }
00776                 }
00777             }
00778         }
00779 
00780         # If we have not already tried and the server supports it and we 
00781         # have a username -- lets try to authenticate.
00782         #
00783         if {![info exists state(auth)]
00784             && [llength [package provide SASL]] != 0
00785             && [set andx [lsearch -glob $response(args) "AUTH*"]] >= 0 
00786             && [string length $options(-username)] > 0 } {
00787             
00788             # May be AUTH mech or AUTH=mech
00789             # We want to use the strongest mechanism that has been offered
00790             # and that we support. If we cannot find a mechanism that 
00791             # succeeds, we will go ahead and try to carry on unauthenticated.
00792             # This may still work else we'll get an unauthorised error later.
00793 
00794             set mechs [string range [lindex $response(args) $andx] 5 end]
00795             foreach mech [SASL::mechanisms] {
00796                 if {[lsearch -exact $mechs $mech] == -1} { continue }
00797                 if {[catch {
00798                     Authenticate $token $mech
00799                 } msg]} {
00800                     if {$options(-debug)} {
00801                         puts stderr "AUTH $mech failed: $msg "
00802                         flush stderr
00803                     }
00804                 }
00805                 if {[info exists state(auth)] && $state(auth)} {
00806                     if {$state(auth) == 1} {
00807                         break
00808                     } else {
00809                         # After successful AUTH we are supposed to redo
00810                         # our connection for mechanisms that setup a new
00811                         # security layer -- these should set state(auth) 
00812                         # greater than 1
00813                         fileevent $state(sd) readable \
00814                             [list ::smtp::readable $token]
00815                         return [initialize_ehlo $token]
00816                     }
00817                 }
00818             }
00819         }
00820         
00821         return $token
00822     } else {
00823         # Bad response; close the connection and hope the next server
00824         # is happier.
00825         catch {close $state(sd)}
00826     }
00827     return {}
00828 }
00829 
00830 ret  ::smtp::SASLCallback (type token , type context , type command , type args) {
00831     upvar #0 $token state
00832     upvar #0 $context ctx
00833     array set options $state(options)
00834     switch -exact -- $command {
00835         login    { return "" }
00836         username { return $options(-username) }
00837         password { return $options(-password) }
00838         hostname { return [info host] }
00839         realm    { 
00840             if {[string equal $ctx(mech) "NTLM"] \
00841                     && [info exists ::env(USERDOMAIN)]} {
00842                 return $::env(USERDOMAIN)
00843             } else {
00844                 return ""
00845             }
00846         }
00847         default  { 
00848             return -code error "error: unsupported SASL information requested"
00849         }
00850     }
00851 }
00852 
00853 ret  ::smtp::Authenticate (type token , type mechanism) {
00854     upvar 0 $token state
00855     package require base64
00856     set ctx [SASL::new -mechanism $mechanism \
00857                  -callback [list [namespace origin SASLCallback] $token]]
00858 
00859     set state(auth) 0
00860     set result [smtp::talk $token 300 "AUTH $mechanism"]
00861     array set response $result
00862 
00863     while {$response(code) == 334} {
00864         # The NTLM initial response is not base64 encoded so handle it.
00865         if {[catch {base64::decode $response(diagnostic)} challenge]} {
00866             set challenge $response(diagnostic)
00867         }
00868         SASL::step $ctx $challenge
00869         set result [smtp::talk $token 300 \
00870                         [base64::encode -maxlen 0 [SASL::response $ctx]]]
00871         array set response $result
00872     }
00873     
00874     if {$response(code) == 235} {
00875         set state(auth) 1
00876         return $result
00877     } else {
00878         return -code 7 $result
00879     }
00880 }
00881 
00882 /*  ::smtp::finalize --*/
00883 /* */
00884 /*  Deletes an SMTP token by closing the connection to the SMTP server,*/
00885 /*        cleanup up various state.*/
00886 /* */
00887 /*  Arguments:*/
00888 /*        token   SMTP token that has an open connection to the SMTP server.*/
00889 /*        args    Optional arguments, where the only useful option is -close,*/
00890 /*                whose valid values are the following:*/
00891 /*                orderly     Normal successful completion.  Close connection and*/
00892 /*                            clear state variables.*/
00893 /*                abort       A connection exists to the SMTP server, but it's in*/
00894 /*                            a weird state and needs to be reset before being*/
00895 /*                            closed.  Then clear state variables.*/
00896 /*                drop        No connection exists, so we just need to clean up*/
00897 /*                            state variables.*/
00898 /* */
00899 /*  Results:*/
00900 /*  SMTP connection is closed and state variables are cleared.  If there's*/
00901 /*        an error while attempting to close the connection to the SMTP server,*/
00902 /*        throw an exception with the error code and error message.*/
00903 
00904 ret  ::smtp::finalize (type token , type args) {
00905     global errorCode errorInfo
00906     # FRINK: nocheck
00907     variable $token
00908     upvar 0 $token state
00909 
00910     array set options [list -close orderly]
00911     array set options $args
00912 
00913     switch -- $options(-close) {
00914         orderly {
00915             set code [catch { talk $token 120 QUIT } result]
00916         }
00917 
00918         abort {
00919             set code [catch {
00920                 talk $token 0 RSET
00921                 talk $token 0 QUIT
00922             } result]
00923         }
00924 
00925         drop {
00926             set code 0
00927             set result ""
00928         }
00929 
00930         default {
00931             error "unknown value for -close $options(-close)"
00932         }
00933     }
00934     set ecode $errorCode
00935     set einfo $errorInfo
00936 
00937     catch { close $state(sd) }
00938 
00939     if {$state(afterID) != ""} {
00940         catch { after cancel $state(afterID) }
00941     }
00942 
00943     foreach name [array names state] {
00944         unset state($name)
00945     }
00946     # FRINK: nocheck
00947     unset $token
00948 
00949     return -code $code -errorinfo $einfo -errorcode $ecode $result
00950 }
00951 
00952 /*  ::smtp::winit --*/
00953 /* */
00954 /*  Send originator info to SMTP server.  This occurs after HELO/EHLO*/
00955 /*        command has completed successfully (in ::smtp::initialize).  This function*/
00956 /*        is called by ::smtp::sendmessageaux.*/
00957 /* */
00958 /*  Arguments:*/
00959 /*        token       SMTP token that has an open connection to the SMTP server.*/
00960 /*        part        MIME token for the message to be sent. May be used for*/
00961 /*                    handling some SMTP extensions.*/
00962 /*        originator  The e-mail address of the entity sending the message,*/
00963 /*                    usually the From clause.*/
00964 /*        mode        SMTP command specifying the mode of communication.  Default*/
00965 /*                    value is MAIL.*/
00966 /* */
00967 /*  Results:*/
00968 /*  Originator info is sent and SMTP server's response is returned.  If an*/
00969 /*        error occurs, throw an exception.*/
00970 
00971 ret  ::smtp::winit (type token , type part , type originator , optional mode =MAIL) {
00972     # FRINK: nocheck
00973     variable $token
00974     upvar 0 $token state
00975 
00976     if {[lsearch -exact [list MAIL SEND SOML SAML] $mode] < 0} {
00977         error "unknown origination mode $mode"
00978     }
00979 
00980     set from "$mode FROM:<$originator>"
00981 
00982     # RFC 1870 -  SMTP Service Extension for Message Size Declaration
00983     if {[info exists state(esmtp)] 
00984         && [lsearch -glob $state(esmtp) "SIZE*"] != -1} {
00985         catch {
00986             set size [string length [mime::buildmessage $part]]
00987             append from " SIZE=$size"
00988         }
00989     }
00990 
00991     array set response [set result [talk $token 600 $from]]
00992 
00993     if {$response(code) == 250} {
00994         set state(addrs) 0
00995         return $result
00996     } else {
00997         return -code 7 $result
00998     }
00999 }
01000 
01001 /*  ::smtp::waddr --*/
01002 /* */
01003 /*  Send recipient info to SMTP server.  This occurs after originator info*/
01004 /*        is sent (in ::smtp::winit).  This function is called by*/
01005 /*        ::smtp::sendmessageaux. */
01006 /* */
01007 /*  Arguments:*/
01008 /*        token       SMTP token that has an open connection to the SMTP server.*/
01009 /*        recipient   One of the recipients to whom the message should be*/
01010 /*                    delivered.  */
01011 /* */
01012 /*  Results:*/
01013 /*  Recipient info is sent and SMTP server's response is returned.  If an*/
01014 /*        error occurs, throw an exception.*/
01015 
01016 ret  ::smtp::waddr (type token , type recipient) {
01017     # FRINK: nocheck
01018     variable $token
01019     upvar 0 $token state
01020 
01021     set result [talk $token 3600 "RCPT TO:<$recipient>"]
01022     array set response $result
01023 
01024     switch -- $response(code) {
01025         250 - 251 {
01026             incr state(addrs)
01027             return $result
01028         }
01029 
01030         default {
01031             return -code 7 $result
01032         }
01033     }
01034 }
01035 
01036 /*  ::smtp::wtext --*/
01037 /* */
01038 /*  Send message to SMTP server.  This occurs after recipient info*/
01039 /*        is sent (in ::smtp::winit).  This function is called by*/
01040 /*        ::smtp::sendmessageaux. */
01041 /* */
01042 /*  Arguments:*/
01043 /*        token       SMTP token that has an open connection to the SMTP server.*/
01044 /*  part        The MIME object containing the message to send.*/
01045 /* */
01046 /*  Results:*/
01047 /*  MIME message is sent and SMTP server's response is returned.  If an*/
01048 /*        error occurs, throw an exception.*/
01049 
01050 ret  ::smtp::wtext (type token , type part) {
01051     # FRINK: nocheck
01052     variable $token
01053     upvar 0 $token state
01054     array set options $state(options)
01055 
01056     set result [talk $token 300 DATA]
01057     array set response $result
01058     if {$response(code) != 354} {
01059         return -code 7 $result
01060     }
01061 
01062     if {[catch { wtextaux $token $part } result]} {
01063         catch { puts -nonewline $state(sd) "\r\n.\r\n" ; flush $state(sd) }
01064         return -code 7 [list code 400 diagnostic $result]
01065     }
01066 
01067     set secs $options(-maxsecs)
01068 
01069     set result [talk $token $secs .]
01070     array set response $result
01071     switch -- $response(code) {
01072         250 - 251 {
01073             return $result
01074         }
01075 
01076         default {
01077             return -code 7 $result
01078         }
01079     }
01080 }
01081 
01082 /*  ::smtp::wtextaux --*/
01083 /* */
01084 /*  Helper function that coordinates writing the MIME message to the socket.*/
01085 /*        In particular, it stacks the channel leading to the SMTP server, sets up*/
01086 /*        some file events, sends the message, unstacks the channel, resets the*/
01087 /*        file events to their original state, and returns.*/
01088 /* */
01089 /*  Arguments:*/
01090 /*        token       SMTP token that has an open connection to the SMTP server.*/
01091 /*  part        The MIME object containing the message to send.*/
01092 /* */
01093 /*  Results:*/
01094 /*  Message is sent.  If anything goes wrong, throw an exception.*/
01095 
01096 ret  ::smtp::wtextaux (type token , type part) {
01097     global errorCode errorInfo
01098 
01099     # FRINK: nocheck
01100     variable $token
01101     upvar 0 $token state
01102 
01103     # Workaround a bug with stacking channels on top of TLS.
01104     # FRINK: nocheck
01105     set trf [set [namespace current]::trf]
01106     if {[info exists state(tls)] && $state(tls)} {
01107         set trf 0
01108     }
01109 
01110     flush $state(sd)
01111     fileevent $state(sd) readable ""
01112     if {$trf} {
01113         transform -attach $state(sd) -command [list ::smtp::wdata $token]
01114     } else {
01115         set state(size) 1
01116     }
01117     fileevent $state(sd) readable [list ::smtp::readable $token]
01118 
01119     # If trf is not available, get the contents of the message,
01120     # replace all '.'s that start their own line with '..'s, and
01121     # then write the mime body out to the filehandle. Do not forget to
01122     # deal with bare LF's here too (SF bug #499242).
01123 
01124     if {$trf} {
01125         set code [catch { ::mime::copymessage $part $state(sd) } result]
01126     } else {
01127         set code [catch { ::mime::buildmessage $part } result]
01128         if {$code == 0} {
01129         # Detect and transform bare LF's into proper CR/LF
01130         # sequences.
01131 
01132         while {[regsub -all -- {([^\r])\n} $result "\\1\r\n" result]} {}
01133             regsub -all -- {\n\.}      $result "\n.."   result
01134 
01135             # Fix for bug #827436 - mail data must end with CRLF.CRLF
01136             if {[string compare [string index $result end] "\n"] != 0} {
01137                 append result "\r\n"
01138             }
01139             set state(size) [string length $result]
01140             puts -nonewline $state(sd) $result
01141             set result ""
01142     }
01143     }
01144     set ecode $errorCode
01145     set einfo $errorInfo
01146 
01147     flush $state(sd)
01148     fileevent $state(sd) readable ""
01149     if {$trf} {
01150         unstack $state(sd)
01151     }
01152     fileevent $state(sd) readable [list ::smtp::readable $token]
01153 
01154     return -code $code -errorinfo $einfo -errorcode $ecode $result
01155 }
01156 
01157 /*  ::smtp::wdata --*/
01158 /* */
01159 /*  This is the custom transform using Trf to do CR/LF translation.  If Trf*/
01160 /*        is not installed on the system, then this function never gets called and*/
01161 /*        no translation occurs.*/
01162 /* */
01163 /*  Arguments:*/
01164 /*        token       SMTP token that has an open connection to the SMTP server.*/
01165 /*        command     Trf provided command for manipulating socket data.*/
01166 /*  buffer      Data to be converted.*/
01167 /* */
01168 /*  Results:*/
01169 /*  buffer is translated, and state(size) is set.  If Trf is not installed*/
01170 /*        on the system, the transform proc defined at the top of this file sets*/
01171 /*        state(size) to 1.  state(size) is used later to determine a timeout*/
01172 /*        value.*/
01173 
01174 ret  ::smtp::wdata (type token , type command , type buffer) {
01175     # FRINK: nocheck
01176     variable $token
01177     upvar 0 $token state
01178 
01179     switch -- $command {
01180         create/write -
01181         clear/write  -
01182         delete/write {
01183             set state(crP) 0
01184             set state(nlP) 1
01185             set state(size) 0
01186         }
01187 
01188         write {
01189             set result ""
01190 
01191             foreach c [split $buffer ""] {
01192                 switch -- $c {
01193                     "." {
01194                         if {$state(nlP)} {
01195                             append result .
01196                         }
01197                         set state(crP) 0
01198                         set state(nlP) 0
01199                     }
01200 
01201                     "\r" {
01202                         set state(crP) 1
01203                         set state(nlP) 0
01204                     }
01205 
01206                     "\n" {
01207                         if {!$state(crP)} {
01208                             append result "\r"
01209                         }
01210                         set state(crP) 0
01211                         set state(nlP) 1
01212                     }
01213 
01214                     default {
01215                         set state(crP) 0
01216                         set state(nlP) 0
01217                     }
01218                 }
01219 
01220                 append result $c
01221             }
01222 
01223             incr state(size) [string length $result]
01224             return $result
01225         }
01226 
01227         flush/write {
01228             set result ""
01229 
01230             if {!$state(nlP)} {
01231                 if {!$state(crP)} {
01232                     append result "\r"
01233                 }
01234                 append result "\n"
01235             }
01236 
01237             incr state(size) [string length $result]
01238             return $result
01239         }
01240 
01241     create/read -
01242         delete/read {
01243         # Bugfix for [#539952]
01244         }
01245 
01246     query/ratio {
01247         # Indicator for unseekable channel,
01248         # for versions of Trf which ask for
01249         # this.
01250         return {0 0}
01251     }
01252     query/maxRead {
01253         # No limits on reading bytes from the channel below, for
01254         # versions of Trf which ask for this information
01255         return -1
01256     }
01257 
01258     default {
01259         # Silently pass all unknown commands.
01260         #error "Unknown command \"$command\""
01261     }
01262     }
01263 
01264     return ""
01265 }
01266 
01267 /*  ::smtp::talk --*/
01268 /* */
01269 /*  Sends an SMTP command to a server*/
01270 /* */
01271 /*  Arguments:*/
01272 /*        token       SMTP token that has an open connection to the SMTP server.*/
01273 /*  secs        Timeout after which command should be aborted.*/
01274 /*        command     Command to send to SMTP server.*/
01275 /* */
01276 /*  Results:*/
01277 /*  command is sent and response is returned.  If anything goes wrong, throw*/
01278 /*        an exception.*/
01279 
01280 ret  ::smtp::talk (type token , type secs , type command) {
01281     # FRINK: nocheck
01282     variable $token
01283     upvar 0 $token state
01284 
01285     array set options $state(options)
01286 
01287     if {$options(-debug)} {
01288         puts stderr "--> $command (wait upto $secs seconds)"
01289         flush stderr
01290     }
01291 
01292     if {[catch { puts -nonewline $state(sd) "$command\r\n"
01293                  flush $state(sd) } result]} {
01294         return [list code 400 diagnostic $result]
01295     }
01296 
01297     if {$secs == 0} {
01298         return ""
01299     }
01300 
01301     return [hear $token $secs]
01302 }
01303 
01304 /*  ::smtp::hear --*/
01305 /* */
01306 /*  Listens for SMTP server's response to some prior command.*/
01307 /* */
01308 /*  Arguments:*/
01309 /*        token       SMTP token that has an open connection to the SMTP server.*/
01310 /*  secs        Timeout after which we should stop waiting for a response.*/
01311 /* */
01312 /*  Results:*/
01313 /*  Response is returned.*/
01314 
01315 ret  ::smtp::hear (type token , type secs) {
01316     # FRINK: nocheck
01317     variable $token
01318     upvar 0 $token state
01319 
01320     array set options $state(options)
01321 
01322     array set response [list args ""]
01323 
01324     set firstP 1
01325     while {1} {
01326         if {$secs >= 0} {
01327         ## SF [ 836442 ] timeout with large data
01328         ## correction, aotto 031105 -
01329         if {$secs > 600} {set secs 600}
01330             set state(afterID) [after [expr {$secs*1000}] \
01331                                       [list ::smtp::timer $token]]
01332         }
01333 
01334         if {!$state(readable)} {
01335             vwait ${token}(readable)
01336         }
01337 
01338         # Wait until socket is readable.
01339         if {$state(readable) !=  -1} {
01340             catch { after cancel $state(afterID) }
01341             set state(afterID) ""
01342         }
01343 
01344         if {$state(readable) < 0} {
01345             array set response [list code 400 diagnostic $state(error)]
01346             break
01347         }
01348         set state(readable) 0
01349 
01350         if {$options(-debug)} {
01351             puts stderr "<-- $state(line)"
01352             flush stderr
01353         }
01354 
01355         if {[string length $state(line)] < 3} {
01356             array set response \
01357                   [list code 500 \
01358                         diagnostic "response too short: $state(line)"]
01359             break
01360         }
01361 
01362         if {$firstP} {
01363             set firstP 0
01364 
01365             if {[scan [string range $state(line) 0 2] %d response(code)] \
01366                     != 1} {
01367                 array set response \
01368                       [list code 500 \
01369                             diagnostic "unrecognizable code: $state(line)"]
01370                 break
01371             }
01372 
01373             set response(diagnostic) \
01374                 [string trim [string range $state(line) 4 end]]
01375         } else {
01376             lappend response(args) \
01377                     [string trim [string range $state(line) 4 end]]
01378         }
01379 
01380         # When status message line ends in -, it means the message is complete.
01381         
01382         if {[string compare [string index $state(line) 3] -]} {
01383             break
01384         }
01385     }
01386 
01387     return [array get response]
01388 }
01389 
01390 /*  ::smtp::readable --*/
01391 /* */
01392 /*  Reads a line of data from SMTP server when the socket is readable.  This*/
01393 /*        is the callback of "fileevent readable".*/
01394 /* */
01395 /*  Arguments:*/
01396 /*        token       SMTP token that has an open connection to the SMTP server.*/
01397 /* */
01398 /*  Results:*/
01399 /*  state(line) contains the line of data and state(readable) is reset.*/
01400 /*        state(readable) gets the following values:*/
01401 /*        -3  if there's a premature eof,*/
01402 /*        -2  if reading from socket fails.*/
01403 /*        1   if reading from socket was successful*/
01404 
01405 ret  ::smtp::readable (type token) {
01406     # FRINK: nocheck
01407     variable $token
01408     upvar 0 $token state
01409 
01410     if {[catch { array set options $state(options) }]} {
01411         return
01412     }
01413 
01414     set state(line) ""
01415     if {[catch { gets $state(sd) state(line) } result]} {
01416         set state(readable) -2
01417         set state(error) $result
01418     } elseif {$result == -1} {
01419         if {[eof $state(sd)]} {
01420             set state(readable) -3
01421             set state(error) "premature end-of-file from server"
01422         }
01423     } else {
01424         # If the line ends in \r, remove the \r.
01425         if {![string compare [string index $state(line) end] "\r"]} {
01426             set state(line) [string range $state(line) 0 end-1]
01427         }
01428         set state(readable) 1
01429     }
01430 
01431     if {$state(readable) < 0} {
01432         if {$options(-debug)} {
01433             puts stderr "    ... $state(error) ..."
01434             flush stderr
01435         }
01436 
01437         catch { fileevent $state(sd) readable "" }
01438     }
01439 }
01440 
01441 /*  ::smtp::timer --*/
01442 /* */
01443 /*  Handles timeout condition on any communication with the SMTP server.*/
01444 /* */
01445 /*  Arguments:*/
01446 /*        token       SMTP token that has an open connection to the SMTP server.*/
01447 /* */
01448 /*  Results:*/
01449 /*  Sets state(readable) to -1 and state(error) to an error message.*/
01450 
01451 ret  ::smtp::timer (type token) {
01452     # FRINK: nocheck
01453     variable $token
01454     upvar 0 $token state
01455 
01456     array set options $state(options)
01457 
01458     set state(afterID) ""
01459     set state(readable) -1
01460     set state(error) "read from server timed out"
01461 
01462     if {$options(-debug)} {
01463         puts stderr "    ... $state(error) ..."
01464         flush stderr
01465     }
01466 }
01467 
01468 /*  ::smtp::boolean --*/
01469 /* */
01470 /*  Helper function for unifying boolean values to 1 and 0.*/
01471 /* */
01472 /*  Arguments:*/
01473 /*        value   Some kind of value that represents true or false (i.e. 0, 1,*/
01474 /*                false, true, no, yes, off, on).*/
01475 /* */
01476 /*  Results:*/
01477 /*  Return 1 if the value is true, 0 if false.  If the input value is not*/
01478 /*        one of the above, throw an exception.*/
01479 
01480 ret  ::smtp::boolean (type value) {
01481     switch -- [string tolower $value] {
01482         0 - false - no - off {
01483             return 0
01484         }
01485 
01486         1 - true - yes - on {
01487             return 1
01488         }
01489 
01490         default {
01491             error "unknown boolean value: $value"
01492         }
01493     }
01494 }
01495 
01496 /*  -------------------------------------------------------------------------*/
01497 
01498 package provide smtp $::smtp::version
01499 
01500 /*  -------------------------------------------------------------------------*/
01501 /*  Local variables:*/
01502 /*  indent-tabs-mode: nil*/
01503 /*  End:*/
01504 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1