[Home]SMTP Client

 ## ******************************************************** 
 ##
 ## Name: smtp.tcl version 1.0
 ##
 ## Description:
 ## Simple Tcl SMTP client.
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ##   mailTo $rcpt(s) $subject $msg $attachment_filename(s)
 ##
 ## Comments:
 ##
 ## ******************************************************** 

 package provide smtp 1.0
 namespace eval smtp {}

 ## fully qualified name of mail relay, if needed
 set ::MAIL_RELAY_FQDN localhost

 ## we have our own client, but sendmail in client
 ## mode is much better! Set to 0 to use marginally
 ## acceptable built in client code.
 set ::USE_SENDMAIL_AS_MTA 1

 ## ******************************************************** 
 ##
 ## Name: mailTo
 ##
 ## Description:
 ## Non-exec'ing version of mailTo from genericAPI.tcl
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc mailTo { { to "" } { subject "" } { body "" } { attach "" } } {

      if { [ string equal $to $subject ] } {
         return -code error "mailTo: missing required arguments"
      }

      set mailhost $::MAIL_RELAY_FQDN
      set user $::env(USER)
      set domain \
         [ join [ lrange [ split $mailhost . ] 1 end ] . ]
      if { ! [ string length $domain ] } {
         set domain localhost
      }

      set from $user@$domain

      if { [ catch {
         ;## since sendmail now supports the crappy greet_pause
         ;## option, slamming is no longer reliable, so we 
         ;## should be configured to use sendmail directly.
         ;## note that greet_pause was INTENDED TO BE DISABLED
         ;## for LOCAL TRAFFIC.
         if { [ info exists ::USE_SENDMAIL_AS_MTA ] && \
              [ string equal 1 $::USE_SENDMAIL_AS_MTA ] } {
            smtp::sendMail $mailhost $from $to $subject $body $attach
          } else {     
            smtp::send $mailhost $from $to $subject $body $attach
          }
      } err ] } {
         return -code error "[ myName ]: $err"
      }

      return {}
 }

 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::send
 ##
 ## Description:
 ## Send mail via Tcl.  Note the 'HELO localhost' call,
 ## which seems to satisfy far more mail relays than it
 ## ever should!
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::send { mailhost from to subject text { attachments "" } } {
      if { [ catch {
         set seqpt {}
         set from [ smtp::validate $from ]
         set to   [ smtp::validate $to   ]
         set seqpt "socket($mailhost,25):"
         set sid  [ socket $mailhost 25  ]
         set seqpt {}
         set from_rx {^\{?\"?([^\"]+)\"?\s+<([^>]+)>\}?$}
         if { [ regexp $from_rx $from -> name address ] } {
            set from "\"$name\" <$address>"
         } else {
            set from [ string trim $from >< ]
            set from "<$from>"
         }

         set date \
            [ clock format [ clock seconds ] \
            -format "%a, %e %b %Y %H:%M:%S %Z" ]

         set id <[ clock seconds ].[ clock clicks ]@localhost>

         fconfigure $sid -buffering line
         fileevent $sid readable [ list smtp::handle gets $sid ]
         smtp::handle puts $sid "HELO localhost"
         set bare [ string trim [ lindex $from end ] >< ]
 	   smtp::handle puts $sid "MAIL From:<$bare>"
         foreach recipient $to {
 	      set recipient [ lindex $recipient end ]
 		 regsub -all {[<>]} $recipient {} recipient
            if { [ regexp {\S+@\S+} $recipient ] && \
 		    ! [ regexp {\s+} $recipient ] } {
 		    smtp::handle puts $sid "RCPT To:<$recipient>"
            }
 	   }

         smtp::handle puts $sid DATA
         smtp::handle puts $sid "From: $from"
         smtp::handle puts $sid "To: [ join $to , ]"
         smtp::handle puts $sid "Subject: $subject"
         smtp::handle puts $sid "Date: $date"
         smtp::handle puts $sid "Message-Id: $id"

 	   set text [ smtp::multipart $text $attachments ]
         foreach line [ split $text "\n" ] {
            smtp::handle puts $sid $line
         }
         smtp::handle puts $sid ".\nQUIT"
         ::close $sid
     } err ] } {
        catch { ::close $sid }
        return -code error "[ myName ]:$seqpt $err"
     }
 }
 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::sendMail
 ##
 ## Description:
 ## Send mail via Tcl.  Note the 'HELO localhost' call,
 ## which seems to satisfy far more mail relays than it
 ## ever should!
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::sendMail { mailhost from to subject text { attachments "" } } {
      if { [ catch {
         set seqpt {}
         set from [ smtp::validate $from ]
         set to   [ smtp::validate $to   ]
         set from_rx {^\{?\"?([^\"]+)\"?\s+<([^>]+)>\}?$}
         if { [ regexp $from_rx $from -> name address ] } {
            set from "\"$name\" <$address>"
         } else {
            set from [ string trim $from >< ]
            set from "<$from>"
         }

         set msg    "From: $from\n"
         append msg "To: [ join $to , ]\n"
         append msg "Subject: $subject\n"

 	   set text [ smtp::multipart $text $attachments ]

         append msg $text

         smtp::useSendmail $msg
     } err ] } {
        catch { ::close $sid }
        return -code error "[ myName ]:$seqpt $err"
     }
 }
 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::useSendmail 
 ##
 ## Description:
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::useSendmail { mail } {

      if { [ catch {

         ;## if this resource variable is defined, use it!
         if { [ info exists ::PATH_TO_SENDMAIL ] } {
            set sendmail $::PATH_TO_SENDMAIL
         } else {
            set sendmail /usr/lib/sendmail
         }

         set pipe [ open "|$sendmail -oi -t" a+ ]
         fconfigure $pipe -blocking off
         fconfigure $pipe -buffering line
         puts $pipe $mail
         ::close $pipe

      } err ] } {
         catch { ::close $pipe }
         return -code error "[ myName ]: $err"
      }
 }
 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::validate
 ##
 ## Description:
 ## Validate email addresses. Only assumes that multiple
 ## addresses are separated by commas.
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::validate { to } {
      set rcpts [ list ]
      set to [ string trim $to ]
      set to [ split $to , ]
 	regsub -all {\s+} $to { } to 
      foreach rcpt $to {
 	   set rcpt [ string trimright $rcpt " >" ]
 	   set rcpt [ lindex $rcpt end ]
         regsub -all {[<>]} $rcpt {} rcpt
 	   set rcpt [ string trim $rcpt ]
         if { ! [ regexp {^\S+@\S+$} $rcpt ] } {
            return -code error "invalid address: '$rcpt'"
         }
         lappend rcpts $rcpt
      }
      return $rcpts
 }
 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::expand
 ##
 ## Description:
 ## If the text submitted was a filename, read the file
 ## and ship it off.
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::expand { text } {
      if { [ catch { 
         if { [ file exists   $text ] && \
 	        [ file readable $text ] } {
            set fid [ open $text r ]
            set text [ read $fid [ file size $text ] ]
            ::close $fid
         }
         append text [ smtp::sig ]
      } err ] } {
         catch { ::close $fid }
         return -code error $err
      }
      return $text
 }
 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::sig
 ##
 ## Description:
 ## Signature file handling, if there is a signature file.
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::sig { { fname .signature } } {
      set signature [ list ]
      if { [ file exists   $fname ] && \
 	     [ file readable $fname ] } {
         set fid [ open    $fname r ]
         set signature \
            [ read $fid [ file size $fname ] ]
         ::close $fid
         set signature "\n\n--\n$signature" 
     }
      return $signature
 }
 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::handle
 ##
 ## Description:
 ## send/receive callback
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::handle { action sid { line "" } } {
      if { [ catch {
 	   fconfigure $sid -blocking off
         $action $sid $line
         fconfigure $sid -blocking on
      } err ] } {
 	   # nothing to be done, socket has gone
 	}
 }
 ## ********************************************************

 ## ******************************************************** 
 ##
 ## Name: smtp::dump
 ##
 ## Description:
 ## Very expensive to run!!
 ##
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::dump { attachment } {

      if { [ catch {
         set rname [ file tail $attachment ]
 	   set data [ exec uuencode $attachment $rname ]
      } err ] } {
         return -code error "[ myName ]: $err"
      }
 	return $data
 }
 ## ******************************************************** 

 ## ******************************************************** 
 ##
 ## Name: smtp::multipart
 ##
 ## Description:
 ## RFC #822 and MIME
 ## Parameters:
 ##
 ## Usage:
 ##
 ## Comments:
 ##

 proc smtp::multipart { text { attachments "" } } {

      if { [ catch {
 	   set attachments [ split $attachments , ]

 	   set boundary TCL_SMTP_attachment_SMTP_TCL

         set text [ smtp::expand $text ]

 	   if { [ string length $attachments ] } {

 	      set    msg "MIME-Version: 1.0\n"
 	      append msg "Content-Type: multipart/mixed;\n"
 	      append msg " boundary=\"$boundary\"\n\n"
 	      append msg "--$boundary\n"
 	      append msg "Content-Type: text/plain; charset=US-ASCII\n\n"
 	      append msg "$text\n"
 	      foreach attachment $attachments {
 	         set attachment [ string trim $attachment ]
 		    if { ! [ file readable $attachment ] } {
 		       set err "attachment not found: '$attachment'"
 		       return -code error $err
 		    }
 	         append msg "--$boundary\n"
 	         append msg "Content-Type: application/octet-stream\n"
 		    append msg "Content-Transfer-Encoding: x-uuencode\n"
 		    append msg "Content-Disposition: attachment;\n"
 		    append msg " filename=\"[ file tail $attachment ]\"\n\n"
 		    append msg [ smtp::dump $attachment ]
 	      }
 	      append msg "\n\n--${boundary}--\n\n"
         } else {
 	      set msg "\n$text"
 	   }
 	} err ] } {
         return -code error "[ myName ]: $err"
      }
 	return $msg
 }
 ## ********************************************************

Silverlake Linux Users Group Wiki | Recent Changes | Preferences
Password required to edit | View other revisions
Last edited March 8, 2005 2:05 pm by Phil (diff)
Search: