PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/tcl/sendmail.tcl

https://bitbucket.org/aolserver/aolserver
TCL | 249 lines | 117 code | 34 blank | 98 comment | 19 complexity | 18c75ccc2e84a48ea94744ae1567c117 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. #
  2. # The contents of this file are subject to the AOLserver Public License
  3. # Version 1.1 (the "License"); you may not use this file except in
  4. # compliance with the License. You may obtain a copy of the License at
  5. # http://aolserver.com/.
  6. #
  7. # Software distributed under the License is distributed on an "AS IS"
  8. # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  9. # the License for the specific language governing rights and limitations
  10. # under the License.
  11. #
  12. # The Original Code is AOLserver Code and related documentation
  13. # distributed by AOL.
  14. #
  15. # The Initial Developer of the Original Code is America Online,
  16. # Inc. Portions created by AOL are Copyright (C) 1999 America Online,
  17. # Inc. All Rights Reserved.
  18. #
  19. # Alternatively, the contents of this file may be used under the terms
  20. # of the GNU General Public License (the "GPL"), in which case the
  21. # provisions of GPL are applicable instead of those above. If you wish
  22. # to allow use of your version of this file only under the terms of the
  23. # GPL and not to allow others to use your version of this file under the
  24. # License, indicate your decision by deleting the provisions above and
  25. # replace them with the notice and other provisions required by the GPL.
  26. # If you do not delete the provisions above, a recipient may use your
  27. # version of this file under either the License or the GPL.
  28. #
  29. #
  30. # $Header$
  31. #
  32. #
  33. # sendmail.tcl - Define the ns_sendmail procedure for sending
  34. # email from a Tcl script through a remote SMTP server.
  35. #
  36. proc _ns_smtp_send {wfp string timeout} {
  37. if {[lindex [ns_sockselect -timeout $timeout {} $wfp {}] 1] == ""} {
  38. error "Timeout writing to SMTP host"
  39. }
  40. puts $wfp $string\r
  41. flush $wfp
  42. }
  43. proc _ns_smtp_recv {rfp check timeout} {
  44. while {1} {
  45. if {[lindex [ns_sockselect -timeout $timeout $rfp {} {}] 0] == ""} {
  46. error "Timeout reading from SMTP host"
  47. }
  48. set line [gets $rfp]
  49. set code [string range $line 0 2]
  50. if {![string match $check $code]} {
  51. error "Expected a $check status line; got:\n$line"
  52. }
  53. if {![string match "-" [string range $line 3 3]]} {
  54. break;
  55. }
  56. }
  57. }
  58. proc ns_sendmail { to from subject body {extraheaders {}} {bcc {}} } {
  59. ## Takes comma-separated values in the "to" parm
  60. ## Multiple To and BCC addresses are handled appropriately.
  61. ## Original ns_sendmail functionality is preserved.
  62. ## Cut out carriage returns
  63. regsub -all "\n" $to "" to
  64. regsub -all "\r" $to "" to
  65. regsub -all "\n" $bcc "" bcc
  66. regsub -all "\r" $bcc "" bcc
  67. ## Split to into a proper list
  68. set tolist_in [split $to ","]
  69. set bcclist_in [split $bcc ","]
  70. ## Get smtp server into, if none then use localhost
  71. set smtp [ns_config ns/parameters smtphost]
  72. if {[string match "" $smtp]} {
  73. set smtp [ns_config ns/parameters mailhost]
  74. }
  75. if {[string match "" $smtp]} {
  76. set smtp localhost
  77. }
  78. set timeout [ns_config ns/parameters smtptimeout]
  79. if {[string match "" $timeout]} {
  80. set timeout 60
  81. }
  82. set smtpport [ns_config ns/parameters smtpport]
  83. if {[string match "" $smtpport]} {
  84. set smtpport 25
  85. }
  86. set tolist [list]
  87. foreach toaddr $tolist_in {
  88. lappend tolist "[string trim $toaddr]"
  89. }
  90. set bcclist [list]
  91. if {![string match "" $bcclist_in]} {
  92. foreach bccaddr $bcclist_in {
  93. lappend bcclist "[string trim $bccaddr]"
  94. }
  95. }
  96. ## Send it along to _ns_sendmail
  97. _ns_sendmail $smtp $smtpport $timeout $tolist $bcclist \
  98. $from $subject $body $extraheaders
  99. }
  100. if { ![nsv_exists ns_sendmail sequence] } {
  101. nsv_set ns_sendmail sequence 0
  102. }
  103. proc _ns_sendmail {smtp smtpport timeout tolist bcclist \
  104. from subject body extraheaders} {
  105. ## Put the tolist in the headers
  106. set rfcto [join $tolist ", "]
  107. ## Build headers
  108. set msg "To: $rfcto\nFrom: $from\nSubject: $subject\nDate: [ns_httptime [ns_time]]"
  109. ## Insert extra headers, if any (not for BCC)
  110. set message_id_already_done_p 0
  111. if {![string match "" $extraheaders]} {
  112. set size [ns_set size $extraheaders]
  113. for {set i 0} {$i < $size} {incr i} {
  114. set key [ns_set key $extraheaders $i]
  115. if { [string equal $key {Message-ID}] } {
  116. set message_id_already_done_p 1
  117. }
  118. append msg "\n${key}: [ns_set value $extraheaders $i]"
  119. }
  120. }
  121. # Insert a unique "Message-ID:" header, but only if the caller did
  122. # not manually include a Message-ID header:
  123. #
  124. # An application could use the Message-ID header for
  125. # e.g. threading support, but we're not trying to do anything
  126. # fancy like that here. We just want to include a globally-unique
  127. # ID. Why? Well, for one thing, since most email user agents
  128. # include a Message-ID, but most SPAM software does not, some
  129. # anti-SPAM software filters out email which does not have a
  130. # Message-ID...
  131. #
  132. # Note: The $message_id below is guaranteed to be globally unique
  133. # if and only if *ALL* of the following conditions are true:
  134. #
  135. # 1. Your unix box's hostname (which is what [ns_info hostname]
  136. # returns) is set to a fully-qualified name like
  137. # "philip.greenspun.com", NOT just a local hostname like
  138. # "philip".
  139. # 2. Your fully-qualified hostname is in fact globally-unique.
  140. # AKA, you didn't do something foolish like set up two separate
  141. # machines that both think their hostname is
  142. # "philip.greenspun.com".
  143. # 3. On your unix host, you have only ONE AOLserver running with
  144. # the server name returned by [ns_info server].
  145. # 4. Since [ns_info boottime] is in seconds, you never restart
  146. # your AOLserver multiple times in < 1 second, jump your system
  147. # clock backwards in time, or etc.
  148. # 5. Once the "ns_sendmail sequence" nsv variable is set, you
  149. # never manually fool with it to re-set it to a previous value.
  150. # While the server is running, this value must always increase,
  151. # never decrease.
  152. #
  153. # --atp@piskorski.com, 2001/10/11 11:51 EDT
  154. # For more info on messgage-id and other email fields, see RFC 2822:
  155. # http://www.faqs.org/rfcs/rfc2822.html
  156. if { ! $message_id_already_done_p } {
  157. set message_id "[nsv_incr ns_sendmail sequence].[ns_info boottime].[ns_info server]@[ns_info hostname]"
  158. append msg "\nMessage-ID: <$message_id>"
  159. }
  160. ## Blank line between headers and body
  161. append msg "\n\n$body\n"
  162. ## Terminate body with a solitary period
  163. foreach line [split $msg "\n"] {
  164. if {[string match . $line]} {
  165. append data .
  166. }
  167. append data $line
  168. append data "\r\n"
  169. }
  170. append data .
  171. ## Open the connection
  172. set sock [ns_sockopen $smtp $smtpport]
  173. set rfp [lindex $sock 0]
  174. set wfp [lindex $sock 1]
  175. ## Strip "from:" email address
  176. regexp {.*<(.*)>} $from ig from
  177. ## Perform the SMTP conversation
  178. if { [catch {
  179. _ns_smtp_recv $rfp 220 $timeout
  180. _ns_smtp_send $wfp "HELO [ns_info hostname]" $timeout
  181. _ns_smtp_recv $rfp 250 $timeout
  182. _ns_smtp_send $wfp "MAIL FROM:<$from>" $timeout
  183. _ns_smtp_recv $rfp 250 $timeout
  184. # TODO: Above, should optionally take a "Return-Path" to use
  185. # as the envelope sender address (aka, envelope return path)
  186. # rather than always using $from. This would allow using
  187. # VERPs, for instance, as discussed at:
  188. # "http://cr.yp.to/proto/verp.txt"
  189. # See also discussion at:
  190. # "http://www.arsdigita.com/bboard/q-and-a-fetch-msg?msg%5fid=000awU"
  191. # --atp@piskorski.com, 2001/10/11 10:25 EDT
  192. ## Loop through To and BCC list via multiple RCPT TO lines
  193. ## A BCC should never, ever appear in the header
  194. foreach toto [concat $tolist $bcclist] {
  195. #transform "Fritz <fritz@foo.com>" into "fritz@foo.com"
  196. regexp {.*<(.*)>} $toto ig toto
  197. _ns_smtp_send $wfp "RCPT TO:<$toto>" $timeout
  198. _ns_smtp_recv $rfp 250 $timeout
  199. }
  200. _ns_smtp_send $wfp DATA $timeout
  201. _ns_smtp_recv $rfp 354 $timeout
  202. _ns_smtp_send $wfp $data $timeout
  203. _ns_smtp_recv $rfp 250 $timeout
  204. _ns_smtp_send $wfp QUIT $timeout
  205. _ns_smtp_recv $rfp 221 $timeout
  206. } errMsg ] } {
  207. ## Error, close and report
  208. close $rfp
  209. close $wfp
  210. return -code error $errMsg
  211. }
  212. ## Close the connection
  213. close $rfp
  214. close $wfp
  215. }