1 /* ========================================================================
2 * Copyright 2008-2009 Mark Crispin
3 * ========================================================================
7 * Program: SSL authentication/encryption module for Windows 9x and NT
11 * Date: 22 September 1998
12 * Last Edited: 9 November 2009
14 * Previous versions of this file were
16 * Copyright 1988-2008 University of Washington
18 * Licensed under the Apache License, Version 2.0 (the "License");
19 * you may not use this file except in compliance with the License.
20 * You may obtain a copy of the License at
22 * http://www.apache.org/licenses/LICENSE-2.0
26 #define SECURITY_WIN32
33 /* in case a binary runs on Windows 2000 */
34 #ifndef ISC_REQ_MANUAL_CRED_VALIDATION
35 #define ISC_REQ_MANUAL_CRED_VALIDATION 0x00080000
37 #ifndef SEC_E_UNTRUSTED_ROOT
38 #define SEC_E_UNTRUSTED_ROOT ((HRESULT) 0x80090325L)
40 #ifndef SEC_E_CERT_EXPIRED
41 #define SEC_E_CERT_EXPIRED ((HRESULT) 0x80090328L)
45 #define SSLBUFLEN 8192
50 typedef struct ssl_stream
{
51 TCPSTREAM
*tcpstream
; /* TCP stream */
52 CredHandle cred
; /* SSL credentials */
53 CtxtHandle context
; /* SSL context */
54 /* stream encryption sizes */
55 SecPkgContext_StreamSizes sizes
;
57 int ictr
; /* input counter */
58 char *iptr
; /* input pointer */
59 int iextractr
; /* extra input counter */
60 char *iextraptr
; /* extra input pointer */
61 char *ibuf
; /* input buffer */
62 char *obuf
; /* output buffer */
68 /* Function prototypes */
70 static SSLSTREAM
*ssl_start(TCPSTREAM
*tstream
,char *host
,unsigned long flags
);
71 static char *ssl_analyze_status (SECURITY_STATUS err
,char *buf
);
72 static char *ssl_getline_work (SSLSTREAM
*stream
,unsigned long *size
,
74 static long ssl_abort (SSLSTREAM
*stream
);
76 /* Secure Sockets Layer network driver dispatch */
78 static struct ssl_driver ssldriver
= {
79 ssl_open
, /* open connection */
80 ssl_aopen
, /* open preauthenticated connection */
81 ssl_getline
, /* get a line */
82 ssl_getbuffer
, /* get a buffer */
83 ssl_soutr
, /* output pushed data */
84 ssl_sout
, /* output string */
85 ssl_close
, /* close connection */
86 ssl_host
, /* return host name */
87 ssl_remotehost
, /* return remote host name */
88 ssl_port
, /* return port number */
89 ssl_localhost
/* return local host name */
92 /* security function table */
93 static SecurityFunctionTable
*sft
= NIL
;
94 static unsigned long ssltsz
= 0;/* SSL maximum token length */
96 /* One-time SSL initialization */
98 static int sslonceonly
= 0;
100 void ssl_onceonlyinit (void)
102 if (!sslonceonly
++) { /* only need to call it once */
108 /* get security library */
109 if (((lib
= LoadLibrary ("schannel.dll")) ||
110 (lib
= LoadLibrary ("security.dll"))) &&
111 (pi
= GetProcAddress (lib
,SECURITY_ENTRYPOINT
)) &&
112 (sft
= (SecurityFunctionTable
*) pi ()) &&
113 !(sft
->EnumerateSecurityPackages (&np
,&pp
))) {
114 /* look for an SSL package */
115 for (i
= 0; (i
< (int) np
); i
++) if (!strcmp (pp
[i
].Name
,UNISP_NAME
)) {
116 /* note maximum token size and name */
117 ssltsz
= pp
[i
].cbMaxToken
;
118 /* apply runtime linkage */
119 mail_parameters (NIL
,SET_SSLDRIVER
,(void *) &ssldriver
);
120 mail_parameters (NIL
,SET_SSLSTART
,(void *) ssl_start
);
121 return; /* all done */
129 * contact service name
130 * contact port number
131 * Returns: SSL stream if success else NIL
134 SSLSTREAM
*ssl_open (char *host
,char *service
,unsigned long port
)
136 TCPSTREAM
*stream
= tcp_open (host
,service
,port
);
137 return stream
? ssl_start (stream
,host
,port
) : NIL
;
141 /* SSL authenticated open
144 * returned user name buffer
145 * Returns: SSL stream if success else NIL
148 SSLSTREAM
*ssl_aopen (NETMBX
*mb
,char *service
,char *usrbuf
)
150 return NIL
; /* don't use this mechanism with SSL */
153 /* Start SSL/TLS negotiations
154 * Accepts: open TCP stream of session
157 * Returns: SSL stream if success else NIL
160 static SSLSTREAM
*ssl_start (TCPSTREAM
*tstream
,char *host
,unsigned long flags
)
165 SecBuffer ibuf
[2],obuf
[1];
166 SecBufferDesc ibufs
,obufs
;
167 char tmp
[MAILTMPLEN
];
169 ULONG req
= ISC_REQ_REPLAY_DETECT
| ISC_REQ_SEQUENCE_DETECT
|
170 ISC_REQ_CONFIDENTIALITY
| ISC_REQ_USE_SESSION_KEY
|
171 ISC_REQ_ALLOCATE_MEMORY
| ISC_REQ_STREAM
| ISC_REQ_EXTENDED_ERROR
+
172 ((flags
& NET_NOVALIDATECERT
) ? ISC_REQ_MANUAL_CRED_VALIDATION
:
173 ISC_REQ_MUTUAL_AUTH
);
174 SCHANNEL_CRED tlscred
;
175 char *buf
= (char *) fs_get (ssltsz
);
176 unsigned long size
= 0;
177 sslfailure_t sf
= (sslfailure_t
) mail_parameters (NIL
,GET_SSLFAILURE
,NIL
);
178 SSLSTREAM
*stream
= (SSLSTREAM
*) memset (fs_get (sizeof (SSLSTREAM
)),0,
180 stream
->tcpstream
= tstream
; /* bind TCP stream */
181 /* initialize TLS credential */
182 memset (&tlscred
,0,sizeof (SCHANNEL_CRED
));
183 tlscred
.dwVersion
= SCHANNEL_CRED_VERSION
;
184 tlscred
.grbitEnabledProtocols
= SP_PROT_TLS1
;
186 /* acquire credentials */
187 if (sft
->AcquireCredentialsHandle
188 (NIL
,UNISP_NAME
,SECPKG_CRED_OUTBOUND
,NIL
,(flags
& NET_TLSCLIENT
) ?
189 &tlscred
: NIL
,NIL
,NIL
,&stream
->cred
,&t
)
190 != SEC_E_OK
) reason
= "Acquire credentials handle failed";
191 else while (!reason
) { /* negotiate security context */
192 /* initialize buffers */
193 ibuf
[0].cbBuffer
= size
; ibuf
[0].pvBuffer
= buf
;
194 ibuf
[1].cbBuffer
= 0; ibuf
[1].pvBuffer
= NIL
;
195 obuf
[0].cbBuffer
= 0; obuf
[0].pvBuffer
= NIL
;
196 ibuf
[0].BufferType
= obuf
[0].BufferType
= SECBUFFER_TOKEN
;
197 ibuf
[1].BufferType
= SECBUFFER_EMPTY
;
198 /* initialize buffer descriptors */
199 ibufs
.ulVersion
= obufs
.ulVersion
= SECBUFFER_VERSION
;
200 ibufs
.cBuffers
= 2; obufs
.cBuffers
= 1;
201 ibufs
.pBuffers
= ibuf
; obufs
.pBuffers
= obuf
;
202 /* negotiate security */
203 e
= sft
->InitializeSecurityContext
204 (&stream
->cred
,size
? &stream
->context
: NIL
,host
,req
,0,
205 SECURITY_NETWORK_DREP
,size
? &ibufs
:NIL
,0,&stream
->context
,&obufs
,&a
,&t
);
206 /* have an output buffer we need to send? */
207 if (obuf
[0].pvBuffer
&& obuf
[0].cbBuffer
) {
208 if (!tcp_sout (stream
->tcpstream
,obuf
[0].pvBuffer
,obuf
[0].cbBuffer
))
209 reason
= "Unexpected TCP output disconnect";
210 /* free the buffer */
211 sft
->FreeContextBuffer (obuf
[0].pvBuffer
);
213 if (!reason
) switch (e
) { /* negotiation state */
214 case SEC_I_INCOMPLETE_CREDENTIALS
:
215 break; /* server wants client auth */
216 case SEC_I_CONTINUE_NEEDED
:
217 if (size
) { /* continue, read any data? */
218 /* yes, anything regurgiated back to us? */
219 if (ibuf
[1].BufferType
== SECBUFFER_EXTRA
) {
220 /* yes, set this as the new data */
221 memmove (buf
,buf
+ size
- ibuf
[1].cbBuffer
,ibuf
[1].cbBuffer
);
222 size
= ibuf
[1].cbBuffer
;
225 size
= 0; /* otherwise, read more stuff from server */
227 case SEC_E_INCOMPLETE_MESSAGE
:
228 /* need to read more data from server */
229 if (!tcp_getdata (stream
->tcpstream
))
230 reason
= "Unexpected TCP input disconnect";
232 memcpy (buf
+size
,stream
->tcpstream
->iptr
,stream
->tcpstream
->ictr
);
233 size
+= stream
->tcpstream
->ictr
;
234 /* empty it from TCP's buffers */
235 stream
->tcpstream
->iptr
+= stream
->tcpstream
->ictr
;
236 stream
->tcpstream
->ictr
= 0;
240 case SEC_E_OK
: /* success, any data to be regurgitated? */
241 if (ibuf
[1].BufferType
== SECBUFFER_EXTRA
) {
242 /* yes, set this as the new data */
243 memmove (stream
->tcpstream
->iptr
= stream
->tcpstream
->ibuf
,
244 buf
+ size
- ibuf
[1].cbBuffer
,ibuf
[1].cbBuffer
);
245 stream
->tcpstream
->ictr
= ibuf
[1].cbBuffer
;
247 if (reason
= ssl_analyze_status
248 (sft
->QueryContextAttributes
249 (&stream
->context
,SECPKG_ATTR_STREAM_SIZES
,&stream
->sizes
),buf
))
250 break; /* error getting sizes */
251 fs_give ((void **) &buf
); /* flush temporary buffer */
252 /* make maximum-sized buffers */
253 stream
->bufsize
= stream
->sizes
.cbHeader
+
254 stream
->sizes
.cbMaximumMessage
+ stream
->sizes
.cbTrailer
;
255 if (stream
->sizes
.cbMaximumMessage
< SSLBUFLEN
)
256 fatal ("cbMaximumMessage is less than SSLBUFLEN!");
257 else if (stream
->sizes
.cbMaximumMessage
< 16384) {
258 sprintf (tmp
,"WINDOWS BUG: cbMaximumMessage = %ld, should be 16384",
259 (long) stream
->sizes
.cbMaximumMessage
);
262 stream
->ibuf
= (char *) fs_get (stream
->bufsize
);
263 stream
->obuf
= (char *) fs_get (stream
->bufsize
);
266 reason
= ssl_analyze_status (e
,buf
);
269 ssl_close (stream
); /* failed to do SSL */
270 stream
= NIL
; /* no stream returned */
271 fs_give ((void **) &buf
); /* flush temporary buffer */
272 switch (*reason
) { /* analyze reason */
273 case '*': /* certificate failure */
274 ++reason
; /* skip over certificate failure indication */
275 /* pass to error callback */
276 if (sf
) (*sf
) (host
,reason
,flags
);
277 else { /* no error callback, build error message */
278 sprintf (tmp
,"Certificate failure for %.80s: %.512s",host
,reason
);
281 case '\0': /* user answered no to certificate callback */
282 if (flags
& NET_TRYSSL
) /* return dummy stream to stop tryssl */
283 stream
= (SSLSTREAM
*) memset (fs_get (sizeof (SSLSTREAM
)),0,
286 default: /* non-certificate failure */
287 if (flags
& NET_TRYSSL
); /* no error output if tryssl */
288 /* pass to error callback */
289 else if (sf
) (*sf
) (host
,reason
,flags
);
290 else { /* no error callback, build error message */
291 sprintf (tmp
,"TLS/SSL failure for %.80s: %.512s",host
,reason
);
299 /* Generate error text from SSL error code
300 * Accepts: SSL status
302 * Returns: text if error status, else NIL
305 static char *ssl_analyze_status (SECURITY_STATUS err
,char *buf
)
308 case SEC_E_OK
: /* no error */
309 case SEC_I_CONTINUE_NEEDED
:
310 case SEC_I_INCOMPLETE_CREDENTIALS
:
311 case SEC_E_INCOMPLETE_MESSAGE
:
313 case SEC_E_NO_AUTHENTICATING_AUTHORITY
:
314 return "*No authority could be contacted for authentication";
315 case SEC_E_WRONG_PRINCIPAL
:
316 return "*Server name does not match certificate";
317 case SEC_E_UNTRUSTED_ROOT
:
318 return "*Self-signed certificate or untrusted authority";
319 case SEC_E_CERT_EXPIRED
:
320 return "*Certificate has expired";
321 case SEC_E_INVALID_TOKEN
:
322 return "Invalid token, probably not an SSL server";
323 case SEC_E_UNSUPPORTED_FUNCTION
:
324 return "SSL not supported on this machine - upgrade your system software";
326 sprintf (buf
,"Unexpected SChannel error %lx - report this",err
);
331 * Accepts: SSL stream
332 * Returns: text line string or NIL if failure
335 char *ssl_getline (SSLSTREAM
*stream
)
337 unsigned long n
,contd
;
338 char *ret
= ssl_getline_work (stream
,&n
,&contd
);
339 if (ret
&& contd
) { /* got a line needing continuation? */
340 STRINGLIST
*stl
= mail_newstringlist ();
341 STRINGLIST
*stc
= stl
;
342 do { /* collect additional lines */
343 stc
->text
.data
= (unsigned char *) ret
;
345 stc
= stc
->next
= mail_newstringlist ();
346 ret
= ssl_getline_work (stream
,&n
,&contd
);
347 } while (ret
&& contd
);
348 if (ret
) { /* stash final part of line on list */
349 stc
->text
.data
= (unsigned char *) ret
;
351 /* determine how large a buffer we need */
352 for (n
= 0, stc
= stl
; stc
; stc
= stc
->next
) n
+= stc
->text
.size
;
353 ret
= fs_get (n
+ 1); /* copy parts into buffer */
354 for (n
= 0, stc
= stl
; stc
; n
+= stc
->text
.size
, stc
= stc
->next
)
355 memcpy (ret
+ n
,stc
->text
.data
,stc
->text
.size
);
358 mail_free_stringlist (&stl
);/* either way, done with list */
363 /* SSL receive line or partial line
364 * Accepts: SSL stream
365 * pointer to return size
366 * pointer to return continuation flag
367 * Returns: text line string, size and continuation flag, or NIL if failure
370 static char *ssl_getline_work (SSLSTREAM
*stream
,unsigned long *size
,
375 *contd
= NIL
; /* assume no continuation */
376 /* make sure have data */
377 if (!ssl_getdata (stream
)) return NIL
;
378 for (s
= stream
->iptr
, n
= 0, c
= '\0'; stream
->ictr
--; n
++, c
= d
) {
379 d
= *stream
->iptr
++; /* slurp another character */
380 if ((c
== '\015') && (d
== '\012')) {
381 ret
= (char *) fs_get (n
--);
382 memcpy (ret
,s
,*size
= n
); /* copy into a free storage string */
383 ret
[n
] = '\0'; /* tie off string with null */
387 /* copy partial string from buffer */
388 memcpy ((ret
= (char *) fs_get (n
)),s
,*size
= n
);
389 /* get more data from the net */
390 if (!ssl_getdata (stream
)) fs_give ((void **) &ret
);
391 /* special case of newline broken by buffer */
392 else if ((c
== '\015') && (*stream
->iptr
== '\012')) {
393 stream
->iptr
++; /* eat the line feed */
395 ret
[*size
= --n
] = '\0'; /* tie off string with null */
397 else *contd
= LONGT
; /* continuation needed */
401 /* SSL receive buffer
402 * Accepts: SSL stream
404 * buffer to read into
405 * Returns: T if success, NIL otherwise
408 long ssl_getbuffer (SSLSTREAM
*stream
,unsigned long size
,char *buffer
)
411 while (size
> 0) { /* until request satisfied */
412 if (!ssl_getdata (stream
)) return NIL
;
413 n
= min (size
,stream
->ictr
);/* number of bytes to transfer */
415 memcpy (buffer
,stream
->iptr
,n
);
416 buffer
+= n
; /* update pointer */
418 size
-= n
; /* update # of bytes to do */
421 buffer
[0] = '\0'; /* tie off string */
426 * Accepts: TCP/IP stream
427 * Returns: T if success, NIL otherwise
430 long ssl_getdata (SSLSTREAM
*stream
)
432 while (stream
->ictr
< 1) { /* decrypted buffer empty? */
433 SECURITY_STATUS status
;
437 size_t n
= 0; /* initially no bytes to decrypt */
438 if (!stream
->tcpstream
) return NIL
;
439 do { /* yes, make sure have data from TCP */
440 if (stream
->iextractr
) { /* have previous unread data? */
441 memcpy (stream
->ibuf
+ n
,stream
->iextraptr
,stream
->iextractr
);
442 n
+= stream
->iextractr
; /* update number of bytes read */
443 stream
->iextractr
= 0; /* no more extra data */
445 else { /* read from TCP */
446 if (!tcp_getdata (stream
->tcpstream
)) return ssl_abort (stream
);
447 /* maximum amount of data to copy */
448 if (!(i
= min (stream
->bufsize
- n
,stream
->tcpstream
->ictr
)))
449 fatal ("incomplete SecBuffer exceeds maximum buffer size");
451 memcpy (stream
->ibuf
+ n
,stream
->tcpstream
->iptr
,i
);
452 stream
->tcpstream
->iptr
+= i
;
453 stream
->tcpstream
->ictr
-= i
;
454 n
+= i
; /* update number of bytes to decrypt */
456 buf
[0].cbBuffer
= n
; /* first SecBuffer gets data */
457 buf
[0].pvBuffer
= stream
->ibuf
;
458 buf
[0].BufferType
= SECBUFFER_DATA
;
459 /* subsequent ones are for spares */
460 buf
[1].BufferType
= buf
[2].BufferType
= buf
[3].BufferType
=
462 msg
.ulVersion
= SECBUFFER_VERSION
;
463 msg
.cBuffers
= 4; /* number of SecBuffers */
464 msg
.pBuffers
= buf
; /* first SecBuffer */
466 } while ((status
= ((DECRYPT_MESSAGE_FN
) sft
->Reserved4
)
467 (&stream
->context
,&msg
,0,NIL
)) == SEC_E_INCOMPLETE_MESSAGE
);
469 case SEC_E_OK
: /* won */
470 case SEC_I_RENEGOTIATE
: /* won but lost it after this buffer */
471 /* hunt for a buffer */
472 for (i
= 0; (i
< 4) && (buf
[i
].BufferType
!= SECBUFFER_DATA
) ; i
++);
473 if (i
< 4) { /* found a buffer? */
474 /* yes, set up pointer and counter */
475 stream
->iptr
= buf
[i
].pvBuffer
;
476 stream
->ictr
= buf
[i
].cbBuffer
;
477 /* any unprocessed data? */
478 while (++i
< 4) if (buf
[i
].BufferType
== SECBUFFER_EXTRA
) {
479 /* yes, note for next time around */
480 stream
->iextraptr
= buf
[i
].pvBuffer
;
481 stream
->iextractr
= buf
[i
].cbBuffer
;
485 default: /* anything else means we've lost */
486 return ssl_abort (stream
);
492 /* SSL send string as record
493 * Accepts: SSL stream
495 * Returns: T if success else NIL
498 long ssl_soutr (SSLSTREAM
*stream
,char *string
)
500 return ssl_sout (stream
,string
,(unsigned long) strlen (string
));
505 * Accepts: SSL stream
508 * Returns: T if success else NIL
511 long ssl_sout (SSLSTREAM
*stream
,char *string
,unsigned long size
)
515 char *s
= stream
->ibuf
;
517 while (size
) { /* until satisfied request */
519 buf
[0].BufferType
= SECBUFFER_STREAM_HEADER
;
520 memset (buf
[0].pvBuffer
= stream
->obuf
,0,
521 buf
[0].cbBuffer
= stream
->sizes
.cbHeader
);
522 /* message (up to maximum size) */
523 buf
[1].BufferType
= SECBUFFER_DATA
;
524 memcpy (buf
[1].pvBuffer
= stream
->obuf
+ stream
->sizes
.cbHeader
,string
,
525 buf
[1].cbBuffer
= min (size
,SSLBUFLEN
));
527 buf
[2].BufferType
= SECBUFFER_STREAM_TRAILER
;
528 memset (buf
[2].pvBuffer
= ((char *) buf
[1].pvBuffer
) + buf
[1].cbBuffer
,0,
529 buf
[2].cbBuffer
= stream
->sizes
.cbTrailer
);
531 buf
[3].BufferType
= SECBUFFER_EMPTY
;
532 msg
.ulVersion
= SECBUFFER_VERSION
;
533 msg
.cBuffers
= 4; /* number of SecBuffers */
534 msg
.pBuffers
= buf
; /* first SecBuffer */
535 string
+= buf
[1].cbBuffer
;
536 size
-= buf
[1].cbBuffer
; /* this many bytes processed */
537 /* encrypt and send message */
538 if ((((ENCRYPT_MESSAGE_FN
) sft
->Reserved3
)
539 (&stream
->context
,0,&msg
,NIL
) != SEC_E_OK
) ||
540 !tcp_sout (stream
->tcpstream
,stream
->obuf
,
541 buf
[0].cbBuffer
+ buf
[1].cbBuffer
+ buf
[2].cbBuffer
))
542 return ssl_abort (stream
);/* encryption or sending failed */
548 * Accepts: SSL stream
551 void ssl_close (SSLSTREAM
*stream
)
553 ssl_abort (stream
); /* nuke the stream */
554 fs_give ((void **) &stream
); /* flush the stream */
559 * Accepts: SSL stream
560 * Returns: NIL always
563 static long ssl_abort (SSLSTREAM
*stream
)
565 if (stream
->tcpstream
) { /* close TCP stream */
566 sft
->DeleteSecurityContext (&stream
->context
);
567 sft
->FreeCredentialHandle (&stream
->cred
);
568 tcp_close (stream
->tcpstream
);
569 stream
->tcpstream
= NIL
;
571 if (stream
->ibuf
) fs_give ((void **) &stream
->ibuf
);
572 if (stream
->obuf
) fs_give ((void **) &stream
->obuf
);
577 * Accepts: SSL stream
578 * Returns: host name for this stream
581 char *ssl_host (SSLSTREAM
*stream
)
583 return stream
? tcp_host (stream
->tcpstream
) : "UNKNOWN";
587 /* SSL get remote host name
588 * Accepts: SSL stream
589 * Returns: host name for this stream
592 char *ssl_remotehost (SSLSTREAM
*stream
)
594 return tcp_remotehost (stream
->tcpstream
);
598 /* SSL return port for this stream
599 * Accepts: SSL stream
600 * Returns: port number for this stream
603 unsigned long ssl_port (SSLSTREAM
*stream
)
605 return tcp_port (stream
->tcpstream
);
609 /* SSL get local host name
610 * Accepts: SSL stream
611 * Returns: local host name
614 char *ssl_localhost (SSLSTREAM
*stream
)
616 return tcp_localhost (stream
->tcpstream
);
619 #include "ssl_none.c" /* currently no server support */