smtp-client.tcl 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. #!/usr/bin/wish
  2. tk scaling 1.2
  3. set serverAddress "ip"
  4. set serverPort "25"
  5. set mailFrom "user@server.domain"
  6. set mailRecipient "user2@server.domain"
  7. set mailSubject "subj"
  8. proc sendSmtpRequest { smtpRequest } {
  9. puts $::serverSocket $smtpRequest
  10. flush $::serverSocket
  11. }
  12. proc unexpectedDataReceiver { } {
  13. if { [eof $::serverSocket] } {
  14. puts "============================================================="
  15. disconnectFromSmtpServer
  16. return
  17. }
  18. puts [ gets $::serverSocket ]
  19. incr ::unexpectedPacketNo
  20. if { $::unexpectedPacketNo > 9 } {
  21. puts "============================================================="
  22. disconnectFromSmtpServer
  23. }
  24. }
  25. proc unexpectedPacketHandler { msg } {
  26. puts "Fatal protocol error. Send mail state is $::sendMailStep."
  27. puts "Undefined data from server:"
  28. puts "============================================================="
  29. puts "$msg"
  30. incr ::sendMailErrorFlag
  31. set ::unexpectedPacketNo 0
  32. set ::sendMailStep "error"
  33. fileevent $::serverSocket readable unexpectedDataReceiver
  34. }
  35. proc unexpectedSmtpCodeHandler { expectedCode } {
  36. incr ::sendMailErrorFlag
  37. puts "SMTP code is $::smtpCode, but uxpected $expectedCode"
  38. puts "msg $::smtpCodeDescription"
  39. puts "Step is $::sendMailStep"
  40. puts "Quiting..."
  41. sendSmtp_QUIT
  42. }
  43. proc sendSmtp_HELO { } {
  44. if { $::smtpCode != 220 } {
  45. unexpectedSmtpCodeHandler 220
  46. return
  47. }
  48. sendSmtpRequest "HELO $::mailFromDomain"
  49. set ::sendMailStep "HELO"
  50. set ::sendMailProgress 30
  51. }
  52. proc sendSmtp_MAIL_FROM { } {
  53. if { $::smtpCode != 250 } {
  54. unexpectedSmtpCodeHandler 250
  55. return
  56. }
  57. sendSmtpRequest "MAIL FROM: <$::mailFrom>"
  58. set ::sendMailStep "MAIL_FROM"
  59. set ::sendMailProgress 40
  60. }
  61. proc sendSmtp_RCPT_TO { } {
  62. if { $::smtpCode != 250 } {
  63. unexpectedSmtpCodeHandler 250
  64. return
  65. }
  66. sendSmtpRequest "RCPT TO: <$::mailRecipient>"
  67. set ::sendMailStep "RCPT_TO"
  68. set ::sendMailProgress 50
  69. }
  70. proc sendSmtp_DATA { } {
  71. if { $::smtpCode != 250 } {
  72. unexpectedSmtpCodeHandler 250
  73. return
  74. }
  75. sendSmtpRequest "DATA"
  76. set ::sendMailStep "DATA"
  77. set ::sendMailProgress 60
  78. }
  79. proc sendSmtp_mail_body { } {
  80. if { $::smtpCode != 354 } {
  81. unexpectedSmtpCodeHandler 354
  82. return
  83. }
  84. set currentDate [ clock format [clock seconds] -format "%a, %d %b %Y %H:%M:%S %z" ]
  85. #mail headers
  86. puts $::serverSocket "From: <$::mailFrom>"
  87. puts $::serverSocket "To: <$::mailRecipient>"
  88. puts $::serverSocket "Subject: $::mailSubject"
  89. puts $::serverSocket "Date: $currentDate"
  90. puts $::serverSocket "MIME-Version: 1.0"
  91. puts $::serverSocket "Content-Transfer-Encoding: 8bit"
  92. puts $::serverSocket "Content-Type: text/plain; charset=\"UTF-8\""
  93. puts $::serverSocket ""
  94. flush $::serverSocket
  95. #mail text
  96. puts $::serverSocket "$::mailText"
  97. flush $::serverSocket
  98. #mail text terminator
  99. puts -nonewline $::serverSocket "\n.\n"
  100. flush $::serverSocket
  101. set ::sendMailStep "mail_body"
  102. set ::sendMailProgress 70
  103. }
  104. proc sendSmtp_QUIT { } {
  105. if { $::smtpCode != 250 } {
  106. puts "It looks like something went wrong. Code is $::smtpCode."
  107. puts "msg $::smtpCodeDescription"
  108. }
  109. sendSmtpRequest "QUIT"
  110. set ::sendMailStep "QUIT"
  111. set ::sendMailProgress 90
  112. }
  113. proc sendSmtp_disconnect { } {
  114. if { $::smtpCode != 221 } {
  115. puts "Warning: unexpected response code on QUIT command."
  116. puts "Code is $::smtpCode, expected 221"
  117. puts "msg $::smtpCodeDescription"
  118. }
  119. disconnectFromSmtpServer
  120. }
  121. proc smtpCodeParser { msg } {
  122. #normal
  123. set matchCount [ regexp {^([0-9]{3})([ -]{1})(.+)$} $msg fullMatch ::smtpCode ::smtpCodeMultiline ::smtpCodeDescription ]
  124. if { $matchCount == 1 } { return }
  125. #only code
  126. set matchCount [ regexp {^[0-9]{3}} $msg ::smtpCode ]
  127. if { $matchCount == 1 } {
  128. set ::smtpCodeDescription ""
  129. set ::smtpCodeMultiline " "
  130. puts "Warning: the received packet does not fully comply with the RFC!"
  131. return
  132. }
  133. unexpectedPacketHandler $msg
  134. }
  135. proc sendMail { } {
  136. if { [ eof $::serverSocket ] } {
  137. puts "Unexpected disconnection from server!"
  138. disconnectFromSmtpServer
  139. return
  140. }
  141. set buf [ gets $::serverSocket ]
  142. smtpCodeParser $buf
  143. switch $::sendMailStep {
  144. "connected" { sendSmtp_HELO }
  145. "HELO" { sendSmtp_MAIL_FROM }
  146. "MAIL_FROM" { sendSmtp_RCPT_TO }
  147. "RCPT_TO" { sendSmtp_DATA }
  148. "DATA" { sendSmtp_mail_body }
  149. "mail_body" { sendSmtp_QUIT }
  150. "QUIT" { sendSmtp_disconnect }
  151. default { }
  152. }
  153. }
  154. proc prepareMail { } {
  155. set matchCount [ regexp -- "^.+@(.+)$" $::mailFrom fullMatch ::mailFromDomain ]
  156. if { $matchCount != 1 } {
  157. throw "error" "e-mail address invalid"
  158. }
  159. set ::mailText [ .tabs.writer.entry get 0.0 end ]
  160. }
  161. proc connect2SmtpServer { } {
  162. set ::sendMailErrorFlag 0
  163. set ::sendMailProgress 0
  164. set ::sendMailStatus ""
  165. prepareMail
  166. set ::serverSocket [ socket $::serverAddress $::serverPort ]
  167. puts "Connected to $::serverAddress:$::serverPort"
  168. fileevent $::serverSocket readable sendMail
  169. set ::sendMailStep "connected"
  170. set ::sendMailProgress 15
  171. set ::sendMailStatus "Processing..."
  172. }
  173. proc disconnectFromSmtpServer { } {
  174. close $::serverSocket
  175. puts "Disconnected from $::serverAddress:$::serverPort"
  176. set ::sendMailStep "disconnected"
  177. set ::sendMailProgress 100
  178. if { $::sendMailErrorFlag > 0 } {
  179. set ::sendMailStatus "Failed."
  180. } else {
  181. set ::sendMailStatus "Done."
  182. }
  183. }
  184. proc buildMainGui { } {
  185. ### TABS ###
  186. ttk::notebook .tabs
  187. grid .tabs -padx 5 -pady 5
  188. ttk::frame .tabs.requisites
  189. ttk::frame .tabs.writer
  190. ttk::frame .tabs.attachments
  191. ttk::frame .tabs.send
  192. .tabs add .tabs.requisites -text "Requisites"
  193. .tabs add .tabs.writer -text "Message"
  194. .tabs add .tabs.attachments -text "Attachments"
  195. .tabs add .tabs.send -text "Send"
  196. ### REQUISITES TAB ###
  197. label .tabs.requisites.fromLabel -anchor e -text "From:"
  198. label .tabs.requisites.toLabel -anchor e -text "To:"
  199. label .tabs.requisites.subjectLabel -anchor e -text "Subject:"
  200. ttk::entry .tabs.requisites.fromEntry -textvariable mailFrom
  201. ttk::entry .tabs.requisites.toEntry -textvariable mailRecipient
  202. ttk::entry .tabs.requisites.subjectEntry -textvariable mailSubject
  203. grid .tabs.requisites.fromLabel -sticky we -padx 5
  204. grid .tabs.requisites.toLabel -sticky we -padx 5
  205. grid .tabs.requisites.subjectLabel -sticky we -padx 5
  206. grid .tabs.requisites.fromEntry -row 0 -column 1 -sticky we -padx 5 -pady 5
  207. grid .tabs.requisites.toEntry -row 1 -column 1 -sticky we -padx 5 -pady 5
  208. grid .tabs.requisites.subjectEntry -row 2 -column 1 -sticky we -padx 5 -pady 5
  209. grid columnconfigure .tabs.requisites 1 -weight 1
  210. ### WRITER TAB ###
  211. text .tabs.writer.entry -yscrollcommand ".tabs.writer.scrollV set" -xscrollcommand ".tabs.writer.scrollH set"
  212. ttk::scrollbar .tabs.writer.scrollV -orient vertical -command ".tabs.writer.entry yview"
  213. ttk::scrollbar .tabs.writer.scrollH -orient horizontal -command ".tabs.writer.entry xview"
  214. grid .tabs.writer.entry -row 0 -column 0
  215. grid .tabs.writer.scrollV -row 0 -column 1 -sticky ns
  216. grid .tabs.writer.scrollH -row 1 -column 0 -sticky we
  217. ### SEND TAB ###
  218. label .tabs.send.addressLabel -text "SMTP server:"
  219. ttk::entry .tabs.send.address -textvariable serverAddress
  220. ttk::entry .tabs.send.port -textvariable serverPort
  221. button .tabs.send.button -text "Send" -command connect2SmtpServer
  222. ttk::progressbar .tabs.send.progress -variable sendMailProgress
  223. ttk::button .tabs.send.cancel -text "Cancel" -command disconnectFromSmtpServer
  224. label .tabs.send.status -textvariable sendMailStatus
  225. grid .tabs.send.addressLabel -row 0 -column 0
  226. grid .tabs.send.address -row 0 -column 1
  227. grid .tabs.send.port -row 0 -column 2
  228. grid .tabs.send.button -row 1 -column 0
  229. grid .tabs.send.progress -row 1 -column 1
  230. grid .tabs.send.cancel -row 1 -column 2
  231. grid .tabs.send.status -row 2 -column 1
  232. #grid rowconfigure .tabs.send 0 -weight 1
  233. grid rowconfigure .tabs.send 1 -weight 1
  234. grid columnconfigure .tabs.send 0 -weight 1
  235. grid columnconfigure .tabs.send 1 -weight 1
  236. ### DEBUG ###
  237. ttk::button .debugButton -text "Debug" -command debugFunction
  238. grid .debugButton
  239. }
  240. bind . <Control-q> { destroy . }
  241. buildMainGui