242 lines
11 KiB
Common Lisp
242 lines
11 KiB
Common Lisp
;;; Copyright (C) 2001, 2003 Eric Marsden
|
|
;;; Copyright (C) 2005 David Lichteblau
|
|
;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
|
|
;;;
|
|
;;; See LICENSE for details.
|
|
|
|
#+xcvb (module (:depends-on ("package")))
|
|
|
|
(in-package :cl+ssl)
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(defconstant +ssl-error-none+ 0)
|
|
(defconstant +ssl-error-ssl+ 1)
|
|
(defconstant +ssl-error-want-read+ 2)
|
|
(defconstant +ssl-error-want-write+ 3)
|
|
(defconstant +ssl-error-want-x509-lookup+ 4)
|
|
(defconstant +ssl-error-syscall+ 5)
|
|
(defconstant +ssl-error-zero-return+ 6)
|
|
(defconstant +ssl-error-want-connect+ 7))
|
|
|
|
|
|
;;; Condition hierarchy
|
|
;;;
|
|
|
|
(defun read-ssl-error-queue ()
|
|
(loop
|
|
:for error-code = (err-get-error)
|
|
:until (zerop error-code)
|
|
:collect error-code))
|
|
|
|
(defun format-ssl-error-queue (stream-designator queue-designator)
|
|
"STREAM-DESIGNATOR is the same as CL:FORMAT accepts: T, NIL, or a stream.
|
|
QUEUE-DESIGNATOR is either a list of error codes (as returned
|
|
by READ-SSL-ERROR-QUEUE) or an SSL-ERROR condition."
|
|
(flet ((body (stream)
|
|
(let ((queue (etypecase queue-designator
|
|
(ssl-error (ssl-error-queue queue-designator))
|
|
(list queue-designator))))
|
|
(format stream "SSL error queue")
|
|
(if queue
|
|
(progn
|
|
(format stream ":~%")
|
|
(loop
|
|
:for error-code :in queue
|
|
:do (format stream "~a~%" (err-error-string error-code (cffi:null-pointer)))))
|
|
(format stream " is empty.")))))
|
|
(case stream-designator
|
|
((t) (body *standard-output*))
|
|
((nil) (let ((s (make-string-output-stream :element-type 'character)))
|
|
(unwind-protect
|
|
(body s)
|
|
(close s))
|
|
(get-output-stream-string s)))
|
|
(otherwise (body stream-designator)))))
|
|
|
|
(define-condition ssl-error (error)
|
|
(
|
|
;; Stores list of error codes
|
|
;; (as returned by the READ-SSL-ERROR-QUEUE function)
|
|
(queue :initform nil :initarg :queue :reader ssl-error-queue)))
|
|
|
|
(define-condition ssl-error/handle (ssl-error)
|
|
((ret :initarg :ret
|
|
:reader ssl-error-ret)
|
|
(handle :initarg :handle
|
|
:reader ssl-error-handle))
|
|
(:report (lambda (condition stream)
|
|
(format stream "Unspecified error ~A on handle ~A"
|
|
(ssl-error-ret condition)
|
|
(ssl-error-handle condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
(define-condition ssl-error-initialize (ssl-error)
|
|
((reason :initarg :reason
|
|
:reader ssl-error-reason))
|
|
(:report (lambda (condition stream)
|
|
(format stream "SSL initialization error: ~A"
|
|
(ssl-error-reason condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
|
|
(define-condition ssl-error-want-something (ssl-error/handle)
|
|
())
|
|
|
|
;;;SSL_ERROR_NONE
|
|
(define-condition ssl-error-none (ssl-error/handle)
|
|
()
|
|
(:documentation
|
|
"The TLS/SSL I/O operation completed. This result code is returned if and
|
|
only if ret > 0.")
|
|
(:report (lambda (condition stream)
|
|
(format stream "The TLS/SSL operation on handle ~A completed. (return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
;; SSL_ERROR_ZERO_RETURN
|
|
(define-condition ssl-error-zero-return (ssl-error/handle)
|
|
()
|
|
(:documentation
|
|
"The TLS/SSL connection has been closed. If the protocol version is SSL 3.0
|
|
or TLS 1.0, this result code is returned only if a closure alert has
|
|
occurred in the protocol, i.e. if the connection has been closed cleanly.
|
|
Note that in this case SSL_ERROR_ZERO_RETURN
|
|
does not necessarily indicate that the underlying transport has been
|
|
closed.")
|
|
(:report (lambda (condition stream)
|
|
(format stream "The TLS/SSL connection on handle ~A has been closed. (return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
;; SSL_ERROR_WANT_READ
|
|
(define-condition ssl-error-want-read (ssl-error-want-something)
|
|
()
|
|
(:documentation
|
|
"The operation did not complete; the same TLS/SSL I/O function should be
|
|
called again later. If, by then, the underlying BIO has data available for
|
|
reading (if the result code is SSL_ERROR_WANT_READ) or allows writing data
|
|
(SSL_ERROR_WANT_WRITE), then some TLS/SSL protocol progress will take place,
|
|
i.e. at least part of an TLS/SSL record will be read or written. Note that
|
|
the retry may again lead to a SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
|
|
condition. There is no fixed upper limit for the number of iterations that
|
|
may be necessary until progress becomes visible at application protocol
|
|
level.")
|
|
(:report (lambda (condition stream)
|
|
(format stream "The TLS/SSL operation on handle ~A did not complete: It wants a READ. (return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
;; SSL_ERROR_WANT_WRITE
|
|
(define-condition ssl-error-want-write (ssl-error-want-something)
|
|
()
|
|
(:documentation
|
|
"The operation did not complete; the same TLS/SSL I/O function should be
|
|
called again later. If, by then, the underlying BIO has data available for
|
|
reading (if the result code is SSL_ERROR_WANT_READ) or allows writing data
|
|
(SSL_ERROR_WANT_WRITE), then some TLS/SSL protocol progress will take place,
|
|
i.e. at least part of an TLS/SSL record will be read or written. Note that
|
|
the retry may again lead to a SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
|
|
condition. There is no fixed upper limit for the number of iterations that
|
|
may be necessary until progress becomes visible at application protocol
|
|
level.")
|
|
(:report (lambda (condition stream)
|
|
(format stream "The TLS/SSL operation on handle ~A did not complete: It wants a WRITE. (return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
;; SSL_ERROR_WANT_CONNECT
|
|
(define-condition ssl-error-want-connect (ssl-error-want-something)
|
|
()
|
|
(:documentation
|
|
"The operation did not complete; the same TLS/SSL I/O function should be
|
|
called again later. The underlying BIO was not connected yet to the peer
|
|
and the call would block in connect()/accept(). The SSL
|
|
function should be called again when the connection is established. These
|
|
messages can only appear with a BIO_s_connect() or
|
|
BIO_s_accept() BIO, respectively. In order to find out, when
|
|
the connection has been successfully established, on many platforms
|
|
select() or poll() for writing on the socket file
|
|
descriptor can be used.")
|
|
(:report (lambda (condition stream)
|
|
(format stream "The TLS/SSL operation on handle ~A did not complete: It wants a connect first. (return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
;; SSL_ERROR_WANT_X509_LOOKUP
|
|
(define-condition ssl-error-want-x509-lookup (ssl-error-want-something)
|
|
()
|
|
(:documentation
|
|
"The operation did not complete because an application callback set by
|
|
SSL_CTX_set_client_cert_cb() has asked to be called again. The
|
|
TLS/SSL I/O function should be called again later. Details depend on the
|
|
application.")
|
|
(:report (lambda (condition stream)
|
|
(format stream "The TLS/SSL operation on handle ~A did not complete: An application callback wants to be called again. (return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
;; SSL_ERROR_SYSCALL
|
|
(define-condition ssl-error-syscall (ssl-error/handle)
|
|
((syscall :initarg :syscall))
|
|
(:documentation
|
|
"Some I/O error occurred. The OpenSSL error queue may contain more
|
|
information on the error. If the error queue is empty (i.e. ERR_get_error() returns 0),
|
|
ret can be used to find out more about the error: If ret == 0, an EOF was observed that
|
|
violates the protocol. If ret == -1, the underlying BIO reported an I/O error (for socket
|
|
I/O on Unix systems, consult errno for details).")
|
|
(:report (lambda (condition stream)
|
|
(if (zerop (length (ssl-error-queue condition)))
|
|
(case (ssl-error-ret condition)
|
|
(0 (format stream "An I/O error occurred: An unexpected EOF was observed on handle ~A. (return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition)))
|
|
(-1 (format stream "An I/O error occurred in the underlying BIO. (return code: ~A)"
|
|
(ssl-error-ret condition)))
|
|
(otherwise (format stream "An I/O error occurred: undocumented reason. (return code: ~A)"
|
|
(ssl-error-ret condition))))
|
|
(format stream "An UNKNOWN I/O error occurred in the underlying BIO. (return code: ~A)"
|
|
(ssl-error-ret condition)))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
;; SSL_ERROR_SSL
|
|
(define-condition ssl-error-ssl (ssl-error/handle)
|
|
()
|
|
(:documentation
|
|
"A failure in the SSL library occurred, usually a protocol error. The
|
|
OpenSSL error queue contains more information on the error.")
|
|
(:report (lambda (condition stream)
|
|
(format stream
|
|
"A failure in the SSL library occurred on handle ~A. (Return code: ~A)"
|
|
(ssl-error-handle condition)
|
|
(ssl-error-ret condition))
|
|
(format-ssl-error-queue stream condition))))
|
|
|
|
(defun ssl-signal-error (handle syscall error-code original-error)
|
|
(let ((queue (read-ssl-error-queue)))
|
|
(if (and (eql error-code #.+ssl-error-syscall+)
|
|
(not (zerop original-error)))
|
|
(error 'ssl-error-syscall
|
|
:handle handle
|
|
:ret error-code
|
|
:queue queue
|
|
:syscall syscall)
|
|
(error (case error-code
|
|
(#.+ssl-error-none+ 'ssl-error-none)
|
|
(#.+ssl-error-ssl+ 'ssl-error-ssl)
|
|
(#.+ssl-error-want-read+ 'ssl-error-want-read)
|
|
(#.+ssl-error-want-write+ 'ssl-error-want-write)
|
|
(#.+ssl-error-want-x509-lookup+ 'ssl-error-want-x509-lookup)
|
|
(#.+ssl-error-zero-return+ 'ssl-error-zero-return)
|
|
(#.+ssl-error-want-connect+ 'ssl-error-want-connect)
|
|
(#.+ssl-error-syscall+ 'ssl-error-zero-return) ; this is intentional here. we got an EOF from the syscall (ret is 0)
|
|
(t 'ssl-error/handle))
|
|
:handle handle
|
|
:ret error-code
|
|
:queue queue))))
|