| | 63 | (define-condition smtp-error (error) |
|---|
| | 64 | ((host :initarg :host :reader host))) |
|---|
| | 65 | |
|---|
| | 66 | (define-condition smtp-protocol-error (smtp-error) |
|---|
| | 67 | ((command :initarg :command :reader command) |
|---|
| | 68 | (expected-response-code :initarg :expected-response-code :reader expected-response-code) |
|---|
| | 69 | (response-code :initarg :response-code :reader response-code) |
|---|
| | 70 | (response-message :initarg :response-message :reader response-message)) |
|---|
| | 71 | (:report (lambda (condition stream) |
|---|
| | 72 | (print-unreadable-object (condition stream :type t) |
|---|
| | 73 | (format stream "while talking to smtp server ~A, a command failed:~%command: ~S expected: ~A response: ~A" |
|---|
| | 74 | (host condition) |
|---|
| | 75 | (command condition) |
|---|
| | 76 | (expected-response-code condition) |
|---|
| | 77 | (response-message condition)))))) |
|---|
| | 78 | |
|---|
| | 79 | (defun smtp-command (stream command expected-response-code &optional (condition-class 'smtp-protocol-error)) |
|---|
| | 80 | (when command |
|---|
| | 81 | (write-to-smtp stream command)) |
|---|
| | 82 | (multiple-value-bind (code msgstr lines) |
|---|
| | 83 | (read-from-smtp stream) |
|---|
| | 84 | (when (/= code expected-response-code) |
|---|
| | 85 | (error condition-class |
|---|
| | 86 | :command command |
|---|
| | 87 | :expected-response-code expected-response-code |
|---|
| | 88 | :response-code code |
|---|
| | 89 | :response-message msgstr)) |
|---|
| | 90 | lines)) |
|---|
| 160 | | (defun open-smtp-connection (stream &key authentication ssl) |
|---|
| 161 | | (multiple-value-bind (code msgstr) |
|---|
| 162 | | (read-from-smtp stream) |
|---|
| 163 | | (when (/= code 220) |
|---|
| 164 | | (error "wrong response from smtp server: ~A" msgstr))) |
|---|
| 165 | | (when ssl |
|---|
| 166 | | (write-to-smtp stream (format nil "EHLO ~A" |
|---|
| 167 | | (usocket::get-host-name))) |
|---|
| 168 | | (multiple-value-bind (code msgstr lines) |
|---|
| 169 | | (read-from-smtp stream) |
|---|
| 170 | | (when (/= code 250) |
|---|
| 171 | | (error "wrong response from smtp server: ~A" msgstr)) |
|---|
| 172 | | (when ssl |
|---|
| 173 | | (cond |
|---|
| 174 | | ((find "STARTTLS" lines :test #'equal) |
|---|
| 175 | | (print-debug "this server supports TLS") |
|---|
| 176 | | (write-to-smtp stream "STARTTLS") |
|---|
| 177 | | (multiple-value-bind (code msgstr) |
|---|
| 178 | | (read-from-smtp stream) |
|---|
| 179 | | (when (/= code 220) |
|---|
| 180 | | (error "Unable to start TLS: ~A" msgstr)) |
|---|
| 181 | | (setf stream |
|---|
| 182 | | #+allegro (socket:make-ssl-client-stream stream) |
|---|
| 183 | | #-allegro |
|---|
| 184 | | (let ((s stream)) |
|---|
| 185 | | (cl+ssl:make-ssl-client-stream |
|---|
| 186 | | (cl+ssl:stream-fd stream) |
|---|
| 187 | | :close-callback (lambda () (close s))))) |
|---|
| 188 | | #-allegro |
|---|
| 189 | | (setf stream (flexi-streams:make-flexi-stream |
|---|
| 190 | | stream |
|---|
| 191 | | :external-format |
|---|
| 192 | | (flexi-streams:make-external-format |
|---|
| 193 | | :latin-1 :eol-style :lf))))) |
|---|
| 194 | | (t |
|---|
| 195 | | (error "this server does not supports TLS")))))) |
|---|
| 196 | | (cond |
|---|
| 197 | | (authentication |
|---|
| 198 | | (write-to-smtp stream (format nil "EHLO ~A" |
|---|
| 199 | | (usocket::get-host-name))) |
|---|
| 200 | | (multiple-value-bind (code msgstr) |
|---|
| 201 | | (read-from-smtp stream) |
|---|
| 202 | | (when (/= code 250) |
|---|
| 203 | | (error "wrong response from smtp server: ~A" msgstr))) |
|---|
| 204 | | (cond |
|---|
| 205 | | ((eq (car authentication) :plain) |
|---|
| 206 | | (write-to-smtp stream (format nil "AUTH PLAIN ~A" |
|---|
| 207 | | (string-to-base64-string |
|---|
| 208 | | (format nil "~A~C~A~C~A" |
|---|
| 209 | | (cadr authentication) |
|---|
| 210 | | #\null (cadr authentication) |
|---|
| 211 | | #\null |
|---|
| 212 | | (caddr authentication))))) |
|---|
| 213 | | (multiple-value-bind (code msgstr) |
|---|
| 214 | | (read-from-smtp stream) |
|---|
| 215 | | (when (/= code 235) |
|---|
| 216 | | (error "plain authentication failed: ~A" msgstr)))) |
|---|
| 217 | | ((eq (car authentication) :login) |
|---|
| 218 | | (write-to-smtp stream "AUTH LOGIN") |
|---|
| 219 | | (multiple-value-bind (code msgstr) |
|---|
| 220 | | (read-from-smtp stream) |
|---|
| 221 | | (when (/= code 334) |
|---|
| 222 | | (error "login authentication failed: ~A" msgstr))) |
|---|
| 223 | | (write-to-smtp stream (string-to-base64-string (cadr authentication))) |
|---|
| 224 | | (multiple-value-bind (code msgstr) |
|---|
| 225 | | (read-from-smtp stream) |
|---|
| 226 | | (when (/= code 334) |
|---|
| 227 | | (error "login authentication send username failed: ~A" msgstr))) |
|---|
| 228 | | (write-to-smtp stream (string-to-base64-string (caddr authentication))) |
|---|
| 229 | | (multiple-value-bind (code msgstr) |
|---|
| 230 | | (read-from-smtp stream) |
|---|
| 231 | | (when (/= code 235) |
|---|
| 232 | | (error "login authentication send password failed: ~A" msgstr)))) |
|---|
| 233 | | (t |
|---|
| 234 | | (error "authentication ~A is not supported in cl-smtp" |
|---|
| 235 | | (car authentication))))) |
|---|
| 236 | | (t |
|---|
| 237 | | (write-to-smtp stream (format nil "HELO ~A" (usocket::get-host-name))) |
|---|
| 238 | | (multiple-value-bind (code msgstr) |
|---|
| 239 | | (read-from-smtp stream) |
|---|
| 240 | | (when (/= code 250) |
|---|
| 241 | | (error "wrong response from smtp server: ~A" msgstr))))) |
|---|
| | 179 | (defun open-smtp-connection (stream &key authentication ssl local-hostname) |
|---|
| | 180 | (smtp-command stream nil |
|---|
| | 181 | 220) |
|---|
| | 182 | (if (or ssl authentication) |
|---|
| | 183 | ;; When SSL or authentication requested, perform ESMTP EHLO |
|---|
| | 184 | (let ((lines (smtp-command stream (format nil "EHLO ~A" local-hostname) |
|---|
| | 185 | 250))) |
|---|
| | 186 | (when ssl |
|---|
| | 187 | (unless (find "STARTTLS" lines :test #'equal) |
|---|
| | 188 | (error "this server does not supports TLS")) |
|---|
| | 189 | (print-debug "this server supports TLS") |
|---|
| | 190 | (smtp-command stream "STARTTLS" |
|---|
| | 191 | 220) |
|---|
| | 192 | (setf stream |
|---|
| | 193 | #+allegro (socket:make-ssl-client-stream stream) |
|---|
| | 194 | #-allegro |
|---|
| | 195 | (let ((s stream)) |
|---|
| | 196 | (cl+ssl:make-ssl-client-stream |
|---|
| | 197 | (cl+ssl:stream-fd stream) |
|---|
| | 198 | :close-callback (lambda () (close s))))) |
|---|
| | 199 | #-allegro |
|---|
| | 200 | (setf stream (flexi-streams:make-flexi-stream |
|---|
| | 201 | stream |
|---|
| | 202 | :external-format |
|---|
| | 203 | (flexi-streams:make-external-format |
|---|
| | 204 | :latin-1 :eol-style :lf)))) |
|---|
| | 205 | (when authentication |
|---|
| | 206 | (ecase (car authentication) |
|---|
| | 207 | (:plain |
|---|
| | 208 | (smtp-command stream (format nil "AUTH PLAIN ~A" |
|---|
| | 209 | (string-to-base64-string |
|---|
| | 210 | (format nil "~A~C~A~C~A" |
|---|
| | 211 | (cadr authentication) |
|---|
| | 212 | #\null (cadr authentication) |
|---|
| | 213 | #\null |
|---|
| | 214 | (caddr authentication)))) |
|---|
| | 215 | 235)) |
|---|
| | 216 | (:login |
|---|
| | 217 | (smtp-command stream "AUTH LOGIN" |
|---|
| | 218 | 334) |
|---|
| | 219 | (smtp-command stream (string-to-base64-string (cadr authentication)) |
|---|
| | 220 | 334) |
|---|
| | 221 | (smtp-command stream (string-to-base64-string (caddr authentication)) |
|---|
| | 222 | 235)) |
|---|
| | 223 | (t |
|---|
| | 224 | (smtp-command stream (format nil "HELO ~A" local-hostname) |
|---|
| | 225 | 250))))) |
|---|
| | 226 | ;; No authentication or SSL requested, perform classic SMTP HELO |
|---|
| | 227 | (smtp-command stream (format nil "HELO ~A" |
|---|
| | 228 | (usocket::get-host-name)) |
|---|
| | 229 | 250)) |
|---|