Skip to content

Commit d906bf5

Browse files
committed
FEAT: smtp scheme: recipient's address validation and possibility to have more than one; Better error handling.
1 parent e6527be commit d906bf5

File tree

1 file changed

+65
-33
lines changed

1 file changed

+65
-33
lines changed

src/mezz/prot-smtp.reb

+65-33
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Rebol [
44
type: module
55
author: ["Graham" "Oldes"]
66
rights: BSD
7-
version: 1.0.1
8-
date: 13-Jul-2022
7+
version: 1.1.0
8+
date: 14-Jul-2022
99
file: %prot-smtp.reb
1010
notes: {
1111
0.0.1 original tested in 2010
@@ -16,20 +16,17 @@ Rebol [
1616
0.0.6 Fixed some bugs in transferring email greater than the buffer size.
1717
1.0.0 Oldes: Updated to work with my Rebol3 fork; including TLS.
1818
1.0.1 Oldes: Using extenal IP in the EHLO message, when domain-name is not available
19+
1.1.0 Oldes: Recipient's address validation and possibility to have more than one
1920
2021
Note that if your password does not work for gmail then you need to
2122
generate an app password. See https://support.google.com/accounts/answer/185833
2223
2324
synchronous mode
2425
write smtp://user:password@smtp.clear.net.nz [
2526
from:
26-
name:
2727
to:
28-
subject:
2928
message:
3029
]
31-
32-
name, and subject are not currently used and may be removed
3330
3431
eg: write smtp://user:password@smtp.yourisp.com compose [
3532
from: me@somewhere.com
@@ -53,7 +50,7 @@ where's my kibble?}]
5350
ehlo: "local.domain.name" ; optional, if not available, external IP will be used
5451
] compose [
5552
from: me@somewhere.com
56-
to: recipient@other.com
53+
to: recipient@other.com
5754
message: (message)
5855
]
5956
@@ -74,8 +71,6 @@ bufsize: 16384 ;-- use a write buffer of 16KiB (maximum TLS record size!) for se
7471
mail-obj: make object! [
7572
from:
7673
to:
77-
name:
78-
subject:
7974
message: none
8075
]
8176

@@ -177,8 +172,8 @@ sync-smtp-handler: function [event][
177172
return false
178173
)
179174
|
180-
thru "AUTH" [#" " | #"="] copy auth-methods: to CRLF to end (
181-
auth-methods: split auth-methods #" "
175+
thru "AUTH" [SP | #"="] copy auth-methods: to CRLF to end (
176+
auth-methods: split auth-methods SP
182177
foreach auth auth-methods [
183178
try [auth: to word! auth]
184179
switch auth [
@@ -252,7 +247,7 @@ sync-smtp-handler: function [event][
252247
; compute challenge response
253248
auth-key: checksum/with auth-key 'md5 spec/pass
254249
sys/log/more 'SMTP "Client: ***auth-key***"
255-
write client to binary! ajoin [enbase/flat ajoin [spec/user #" " lowercase enbase auth-key 16] 64 CRLF]
250+
write client to binary! ajoin [enbase/flat ajoin [spec/user SP lowercase enbase auth-key 16] 64 CRLF]
256251
smtp-port/state: 'PASSWORD
257252
false
258253
][
@@ -262,30 +257,44 @@ sync-smtp-handler: function [event][
262257
PLAIN
263258
PASSWORD [
264259
either code = 235 [
260+
write client to binary! net-log/C ajoin ["MAIL FROM: " mold as tag! smtp-ctx/mail/from CRLF]
265261
smtp-port/state: 'FROM
266-
write client to binary! net-log/C ajoin ["MAIL FROM: <" smtp-ctx/mail/from ">" CRLF ]
262+
smtp-ctx/recipients: 0
267263
false
268264
][
269265
throw-smtp-error smtp-port "Failed authentication"
270266
]
271267
]
272-
FROM [
273-
either code = 250 [
274-
write client to binary! net-log/C ajoin ["RCPT TO: <" smtp-ctx/mail/to ">" crlf]
275-
smtp-port/state: 'TO
276-
false
277-
] [
278-
throw-smtp-error smtp-port "Rejected by server"
268+
FROM
269+
RCPT [
270+
if code <> 250 [
271+
either state == 'FROM [
272+
throw-smtp-error smtp-port "FROM address rejected by server"
273+
return true ; awake.. no more job to do.
274+
][
275+
sys/log/error 'SMTP ["Server rejects TO address:" as-red smtp-ctx/rcpt]
276+
smtp-ctx/rcpt: none
277+
smtp-ctx/recipients: smtp-ctx/recipients - 1
278+
]
279279
]
280-
]
281-
TO [
282-
either code = 250 [
283-
smtp-port/state: 'DATA
280+
either empty? smtp-ctx/mail/to [
281+
;; no more recipients, check if at least one was accepted...
282+
;sys/log/debug 'SMTP ["Number of accepted recipients:" smtp-ctx/recipients]
283+
if smtp-ctx/recipients == 0 [
284+
throw-smtp-error smtp-port "There were no accepted recipients!"
285+
return true
286+
]
287+
;; if so, request the DATA start...
284288
write client to binary! net-log/C join "DATA" CRLF
285-
false
286-
] [
287-
throw-smtp-error smtp-port "Server rejects TO address"
289+
smtp-port/state: 'DATA
290+
][
291+
;; register another recipient...
292+
smtp-ctx/rcpt: take smtp-ctx/mail/to
293+
smtp-ctx/recipients: smtp-ctx/recipients + 1
294+
write client to binary! net-log/C ajoin ["RCPT TO: " mold as tag! smtp-ctx/rcpt crlf]
295+
smtp-port/state: 'RCPT
288296
]
297+
false
289298
]
290299
DATA [
291300
either code = 354 [
@@ -347,16 +356,36 @@ sync-smtp-handler: function [event][
347356
sync-write: func [
348357
port [port!]
349358
body [block!]
350-
/local ctx result
359+
/local ctx result rcpt error
351360
][
352361
sys/log/debug 'SMTP ["sync-write state:" port/state]
362+
363+
;; there may be multiple recipients...
364+
;; do validation before actually opening the connection.
365+
rcpt: select body 'to
366+
case/all [
367+
block? :rcpt [
368+
;; only emails are valid here, so remove everything else...
369+
rcpt: copy rcpt
370+
remove-each m rcpt [not email? m]
371+
]
372+
email? :rcpt [
373+
rcpt: to block! rcpt
374+
]
375+
any [not block? :rcpt empty? :rcpt] [
376+
throw-smtp-error port "There must be at least one recipient!"
377+
return true
378+
]
379+
]
380+
353381
unless ctx: port/extra [
354382
open port
355383
ctx: port/extra
356384
port/state: 'READY
357385
]
358386
; construct the email object from the specs
359387
ctx/mail: construct/with body mail-obj
388+
ctx/mail/to: :rcpt
360389

361390
ctx/connection/awake: :sync-smtp-handler
362391

@@ -375,6 +404,10 @@ sync-write: func [
375404
if port/state = 'CLOSE [
376405
close port
377406
]
407+
;print "sync-write DONE"
408+
if all [port port/extra error? port/extra/error][
409+
do port/extra/error
410+
]
378411
true
379412
]
380413

@@ -402,6 +435,8 @@ sys/make-scheme [
402435
connection:
403436
mail:
404437
error:
438+
rcpt: ;= used to store the last requested RCPT address
439+
recipients: ;= number of accepted recipients (must be at least one to proceed data sending)
405440
]
406441
spec: port/spec
407442
; create the tcp port and set it to port/state/connection
@@ -468,17 +503,14 @@ sys/make-scheme [
468503
sync-write port body
469504
]
470505
]
471-
awake: func[event /local port type error][
506+
awake: func[event /local port type][
472507
port: event/port
473508
type: event/type
474509
sys/log/debug 'SMTP ["SMTP-Awake event:" type]
475510
switch/default type [
476511
error [
477-
error: all [port/extra port/extra/error]
478-
close port
479-
wait [port 0.1]
480-
do error
481512
port/state: 'ERROR
513+
try [ close port/extra/connection ]
482514
true
483515
]
484516
close [

0 commit comments

Comments
 (0)