|
28 | 28 | (placeholder '(sendmail-connected? connection) "Should return true if the connection is still open") |
29 | 29 |
|
30 | 30 | ; Send Mail |
31 | | - (placeholder '(sendmail-send-message-with-options connection message-content from to-list message-options to-list-options) "Send a message to the server. The message-options and the to-list-options are optional. The to-list-options is a list of option tuple lists, one for each receiver. The function should return a list corresponding to the responses from the server for each recipient in the to-list. If there is only a single response, a list with a single response should be returned.") |
| 31 | + (placeholder '(sendmail-send-message-full connection message-content from to-list cc-list bcc-list headers message-options to-list-options) "Send a message to the server. The cc-list and bcc-list are optional. The headers hash is optional. The message-options and the to-list-options are optional. The to-list-options is a list of option tuple lists, one for each receiver. The function should return a list corresponding to the responses from the server for each recipient in the to-list. If there is only a single response, a list with a single response should be returned.") |
| 32 | + |
| 33 | + (define (sendmail-send-message-with-options connection message-content from to-list message-options to-list-options) |
| 34 | + (sendmail-send-message-full connection message-content from to-list '() '() (make-hash-table) message-options to-list-options)) |
32 | 35 | (define (sendmail-send-message connection content from to-list) |
33 | 36 | (sendmail-send-message-with-options connection content from to-list '() '())) |
| 37 | + (define (sendmail-send-message-with-headers connection content from to-list headers-hash-map) |
| 38 | + (sendmail-send-message-full connection content from to-list '() '() headers-hash-map '() '())) |
34 | 39 |
|
35 | 40 | ; Response accessors |
36 | 41 | (placeholder '(send-success? response) "Return whether the sending was successful.") |
|
59 | 64 | (define (compile-options-string options) |
60 | 65 | (string-join (compile-options-strings options) " ")) |
61 | 66 |
|
| 67 | + (define (server-message-contains? content) |
| 68 | + (string-contains? (server-message-data server) content)) |
62 | 69 |
|
63 | 70 | (setup (lambda () |
64 | 71 | (set! server '()))) |
|
142 | 149 |
|
143 | 150 | (setup (lambda () |
144 | 151 | (set! server (start-mock-server)) |
145 | | - (connect-smtp-server) |
146 | | - )) |
| 152 | + (connect-smtp-server))) |
147 | 153 |
|
148 | 154 | (tearDown (lambda () |
149 | 155 | (sendmail-disconnect smtp-connection) |
|
170 | 176 | (server-requests-with-command server "MAIL")) |
171 | 177 | (string-append "Expected client to send empty sender: " (server-requests-with-command server "MAIL")))))) |
172 | 178 |
|
| 179 | + ; TODO: CC and BCC |
| 180 | + |
173 | 181 | ; TODO |
174 | 182 | ;(test "Send message with a valid and an invalid recipient" (lambda () |
175 | 183 | ; (let |
|
229 | 237 |
|
230 | 238 | )) |
231 | 239 |
|
232 | | - (capability 'crlf-injection-detection (list |
| 240 | + (capability 'headers (list |
| 241 | + |
| 242 | + (define smtp-connection '()) |
| 243 | + (define (connect-smtp-server) |
| 244 | + (set! smtp-connection (sendmail-connect "localhost" (server-port server)))) |
| 245 | + |
| 246 | + (setup (lambda () |
| 247 | + (set! server (start-mock-server)) |
| 248 | + (connect-smtp-server))) |
| 249 | + |
| 250 | + (tearDown (lambda () |
| 251 | + (sendmail-disconnect smtp-connection) |
| 252 | + (set! smtp-connection '()))) |
| 253 | + |
| 254 | + (test "set basic header" (lambda () |
| 255 | + (sendmail-send-message-with-headers smtp-connection "message content" "sender@sender.to" '("user@recipient.to") (alist->hash-table '(("x-my-header" "some value")))) |
| 256 | + (assert (server-message-contains? "x-my-header: some value") (string-append "Expected x-my-header to be present, but instead got " (server-message-data server))))) |
| 257 | + |
| 258 | + (test "set header with unicode value" (lambda () |
| 259 | + (sendmail-send-message-with-headers smtp-connection "message content" "sender@sender.to" '("user@recipient.to") (alist->hash-table '(("x-my-header" "¡some value")))) |
| 260 | + (assert |
| 261 | + (or |
| 262 | + (server-message-contains? "x-my-header: =?utf-8?q?=C2=A1some?= value") |
| 263 | + (server-message-contains? "x-my-header: =?utf-8?b?wqFzb21lIHZhbHVl?=") |
| 264 | + (server-message-contains? "x-my-header: =?UTF-8?Q?=C2=A1some?= value") |
| 265 | + (server-message-contains? "x-my-header: =?UTF-8?B?wqFzb21lIHZhbHVl?=")) |
| 266 | + (string-append "Expected x-my-header to contain encoded ¡, but instead got " (server-message-data server))) |
| 267 | + (assert |
| 268 | + (server-message-contains? "message content") |
| 269 | + (string-append "Expected message content to be unaltered by unicode header value, but instead got" (server-message-data server))))) |
| 270 | + |
| 271 | + (test "set override standard header" (lambda () |
| 272 | + (sendmail-send-message-with-headers smtp-connection "message content" "sender@sender.to" '("user@recipient.to") (alist->hash-table '(("To" "another-user@recipient.to")))) |
| 273 | + (assert (server-message-contains? "To: another-user@recipient.to") (string-append "Expected To header field to include another-user..., but instead got " (server-message-data server))))) |
| 274 | + |
| 275 | + (capability 'crlf-injection (list |
| 276 | + |
| 277 | + (capability 'detection (list |
| 278 | + |
| 279 | + (data-test "CRLF detection in basic header value" |
| 280 | + '(("1some\rvalue") ("2some\nvalue") ("3some\r\nvalue")) |
| 281 | + (lambda (header-value) |
| 282 | + (let |
| 283 | + ((responses (sendmail-send-message-with-headers smtp-connection "message content" "sender@sender.to" '("user@recipient.to") (alist->hash-table (list (list "x-my-header" header-value)))))) |
| 284 | + (assert (all? send-error? responses) (string-append "The header value: " header-value " should have resulted in error but did not:" responses))))) |
| 285 | + |
| 286 | + (data-test "CRLF detection in unicode header value" |
| 287 | + '(("1¡some\rvalue") ("2¡some\nvalue") ("3¡some\r\nvalue")) |
| 288 | + (lambda (header-value) |
| 289 | + (let |
| 290 | + ((responses (sendmail-send-message-with-headers smtp-connection "message content" "sender@sender.to" '("user@recipient.to") (alist->hash-table (list (list "x-my-header" header-value)))))) |
| 291 | + (assert (all? send-error? responses) (string-append "The header value: " header-value " should have resulted in error but did not:" responses))))) |
| 292 | + |
| 293 | + )) |
| 294 | + |
| 295 | + (capability 'mitigation (list |
| 296 | + |
| 297 | + (data-test "CRLF mitigation in basic header value" |
| 298 | + '(("1some\rvalue") ("2some\nvalue") ("3some\r\nvalue")) |
| 299 | + (lambda (header-value) |
| 300 | + (let |
| 301 | + ((responses (sendmail-send-message-with-headers smtp-connection "message content" "sender@sender.to" '("user@recipient.to") (alist->hash-table (list (list "x-my-header" header-value)))))) |
| 302 | + (assert (all? send-success? responses) (string-append "Message was not sent successfully, instead got: " responses)) |
| 303 | + (assert (not (server-message-contains? header-value)) (string-append "The header value: " header-value " should have been stripped of CRLF but did not:" (server-message-data server)))))) |
| 304 | + |
| 305 | + (data-test "CRLF mitigation in unicode header value" |
| 306 | + '(("1¡some \rvalue" "\rvalue") ("2¡some \nvalue" "\nvalue") ("3¡some \r\nvalue" "\r\nvalue")) |
| 307 | + (lambda (header-value fragment) |
| 308 | + (let |
| 309 | + ((responses (sendmail-send-message-with-headers smtp-connection "message content" "sender@sender.to" '("user@recipient.to") (alist->hash-table (list (list "x-my-header" header-value)))))) |
| 310 | + (assert (all? send-success? responses) (string-append "Message was not sent successfully, instead got: " responses)) |
| 311 | + (assert (not (server-message-contains? fragment)) (string-append "The header value: " header-value " should have been stripped of CRLF but did not: " (server-message-data server)))))) |
| 312 | + |
| 313 | + )) |
| 314 | + )) |
| 315 | + |
| 316 | + )) |
| 317 | + |
| 318 | + (capability 'general-crlf-injection (list |
233 | 319 |
|
234 | 320 | (define smtp-connection '()) |
235 | 321 | (define (connect-smtp-server) |
|
320 | 406 | (sendmail-disconnect smtp-connection) |
321 | 407 | (set! smtp-connection '()))) |
322 | 408 |
|
323 | | - (define (server-message-data-contains content) |
324 | | - (string-contains? content (server-message-data server))) |
325 | | - |
326 | 409 | (capability '8bitmime (list |
327 | 410 |
|
328 | 411 | (define (activate-8bitmime server) |
|
346 | 429 | smtp-connection "¡a test message containing unicode!" "sender@sender.com" '("user@recipient.com") '() '()))) |
347 | 430 | (assert (all? send-success? send-message-responses)) |
348 | 431 | (assert |
349 | | - (not (server-message-data-contains "¡a test message containing unicode!"))) |
| 432 | + (not (server-message-contains? "¡a test message containing unicode!"))) |
350 | 433 | (string-append |
351 | 434 | "Expected server to receive message with unicode content encoded, but received: " (server-message-data server))))) |
352 | 435 |
|
|
358 | 441 | (assert (all? send-success? send-message-responses)) |
359 | 442 | (assert-any-request server "MAIL FROM:<sender@sender.com> BODY=8BITMIME") |
360 | 443 | (assert |
361 | | - (server-message-contains "¡a test message containing unicode!") |
| 444 | + (server-message-contains? "¡a test message containing unicode!") |
362 | 445 | (string-append |
363 | 446 | "Expected server to receive message with unicode content directly, but received: " (server-message-data server)))))) |
364 | 447 |
|
|
373 | 456 | smtp-connection "¡a test message containing unicode!" "sender@sender.com" '("user@recipient.com") '() '()))) |
374 | 457 | (assert-any-request server "MAIL FROM:<sender@sender.com>") |
375 | 458 | (assert |
376 | | - (not (server-message-contains "¡a test message containing unicode!")) |
| 459 | + (not (server-message-contains? "¡a test message containing unicode!")) |
377 | 460 | (string-append |
378 | 461 | "Expected server to receive message with unicode content directly, but received: " (server-message-data server)))))) |
379 | 462 |
|
|
385 | 468 | (assert (all? send-success? send-message-responses)) |
386 | 469 | (assert-any-request server "MAIL FROM:<sender@sender.com> BODY=8BITMIME") |
387 | 470 | (assert |
388 | | - (server-message-contains "¡a test message containing unicode!") |
| 471 | + (server-message-contains? "¡a test message containing unicode!") |
389 | 472 | (string-append |
390 | 473 | "Expected server to receive message with unicode content directly, but received: " (server-message-data server)))))) |
391 | 474 |
|
|
0 commit comments