4
4
type: module
5
5
author: ["Graham" "Oldes" ]
6
6
rights: BSD
7
- version: 1.0.1
8
- date: 13 -Jul-2022
7
+ version: 1.1.0
8
+ date: 14 -Jul-2022
9
9
file: %prot-smtp.reb
10
10
notes: {
11
11
0.0.1 original tested in 2010
@@ -16,20 +16,17 @@ Rebol [
16
16
0.0.6 Fixed some bugs in transferring email greater than the buffer size.
17
17
1.0.0 Oldes: Updated to work with my Rebol3 fork; including TLS.
18
18
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
19
20
20
21
Note that if your password does not work for gmail then you need to
21
22
generate an app password. See https://support.google.com/accounts/answer/185833
22
23
23
24
synchronous mode
24
25
write smtp://user:password@smtp.clear.net.nz [
25
26
from:
26
- name:
27
27
to:
28
- subject:
29
28
message:
30
29
]
31
-
32
- name, and subject are not currently used and may be removed
33
30
34
31
eg: write smtp://user:password@smtp.yourisp.com compose [
35
32
from: me@somewhere.com
@@ -53,7 +50,7 @@ where's my kibble?}]
53
50
ehlo: "local.domain.name" ; optional, if not available, external IP will be used
54
51
] compose [
55
52
from: me@somewhere.com
56
- to: recipient@other.com
53
+ to: recipient@other.com
57
54
message: (message)
58
55
]
59
56
@@ -74,8 +71,6 @@ bufsize: 16384 ;-- use a write buffer of 16KiB (maximum TLS record size!) for se
74
71
mail-obj: make object! [
75
72
from:
76
73
to:
77
- name:
78
- subject:
79
74
message: none
80
75
]
81
76
@@ -177,8 +172,8 @@ sync-smtp-handler: function [event][
177
172
return false
178
173
)
179
174
|
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
182
177
foreach auth auth-methods [
183
178
try [auth: to word! auth]
184
179
switch auth [
@@ -252,7 +247,7 @@ sync-smtp-handler: function [event][
252
247
; compute challenge response
253
248
auth-key: checksum /with auth-key 'md5 spec/pass
254
249
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]
256
251
smtp-port/state: 'PASSWORD
257
252
false
258
253
][
@@ -262,30 +257,44 @@ sync-smtp-handler: function [event][
262
257
PLAIN
263
258
PASSWORD [
264
259
either code = 235 [
260
+ write client to binary! net-log/C ajoin ["MAIL FROM: " mold as tag! smtp-ctx/mail/from CRLF]
265
261
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
267
263
false
268
264
][
269
265
throw-smtp-error smtp-port "Failed authentication"
270
266
]
271
267
]
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
+ ]
279
279
]
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...
284
288
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
288
296
]
297
+ false
289
298
]
290
299
DATA [
291
300
either code = 354 [
@@ -347,16 +356,36 @@ sync-smtp-handler: function [event][
347
356
sync-write : func [
348
357
port [port! ]
349
358
body [block! ]
350
- /local ctx result
359
+ /local ctx result rcpt error
351
360
] [
352
361
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
+
353
381
unless ctx: port/extra [
354
382
open port
355
383
ctx: port/extra
356
384
port/state: 'READY
357
385
]
358
386
; construct the email object from the specs
359
387
ctx/mail: construct /with body mail-obj
388
+ ctx/mail/to: :rcpt
360
389
361
390
ctx/connection/awake: :sync-smtp-handler
362
391
@@ -375,6 +404,10 @@ sync-write: func [
375
404
if port/state = 'CLOSE [
376
405
close port
377
406
]
407
+ ;print "sync-write DONE"
408
+ if all [port port/extra error? port/extra/error ][
409
+ do port/extra/error
410
+ ]
378
411
true
379
412
]
380
413
@@ -402,6 +435,8 @@ sys/make-scheme [
402
435
connection:
403
436
mail:
404
437
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)
405
440
]
406
441
spec: port/spec
407
442
; create the tcp port and set it to port/state/connection
@@ -468,17 +503,14 @@ sys/make-scheme [
468
503
sync-write port body
469
504
]
470
505
]
471
- awake : func [ event /local port type error ] [
506
+ awake : func [ event /local port type] [
472
507
port: event/port
473
508
type: event/type
474
509
sys/log/debug 'SMTP ["SMTP-Awake event:" type]
475
510
switch /default type [
476
511
error [
477
- error: all [port/extra port/extra/error ]
478
- close port
479
- wait [port 0.1 ]
480
- do error
481
512
port/state: 'ERROR
513
+ try [ close port/extra/connection ]
482
514
true
483
515
]
484
516
close [
0 commit comments