* Several changes to the compilation of Alpine in Windows to use
[alpine.git] / imap / src / osdep / nt / ssl_old.c
blob73a360df30c948a6023ab4190def58f0d8d21be8
1 /* ========================================================================
2 * Copyright 2008-2009 Mark Crispin
3 * ========================================================================
4 */
6 /*
7 * Program: SSL authentication/encryption module for Windows 9x and NT
9 * Author: Mark Crispin
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
27 #include <sspi.h>
28 #include <wincrypt.h>
29 ALGIDDEF
30 #include <schnlsp.h>
31 #include <issperr.h>
33 /* in case a binary runs on Windows 2000 */
34 #ifndef ISC_REQ_MANUAL_CRED_VALIDATION
35 #define ISC_REQ_MANUAL_CRED_VALIDATION 0x00080000
36 #endif
37 #ifndef SEC_E_UNTRUSTED_ROOT
38 #define SEC_E_UNTRUSTED_ROOT ((HRESULT) 0x80090325L)
39 #endif
40 #ifndef SEC_E_CERT_EXPIRED
41 #define SEC_E_CERT_EXPIRED ((HRESULT) 0x80090328L)
42 #endif
45 #define SSLBUFLEN 8192
48 /* SSL I/O stream */
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;
56 size_t bufsize;
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 */
63 } SSLSTREAM;
65 #include "sslio.h"
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,
73 long *contd);
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 */
103 HINSTANCE lib;
104 FARPROC pi;
105 ULONG np;
106 SecPkgInfo *pp;
107 int i;
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 */
127 /* SSL open
128 * Accepts: host name
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
142 * Accepts: host name
143 * service name
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
155 * user's host name
156 * flags
157 * Returns: SSL stream if success else NIL
160 static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
162 SECURITY_STATUS e;
163 ULONG a;
164 TimeStamp t;
165 SecBuffer ibuf[2],obuf[1];
166 SecBufferDesc ibufs,obufs;
167 char tmp[MAILTMPLEN];
168 char *reason = NIL;
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,
179 sizeof (SSLSTREAM));
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;
223 break;
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";
231 else {
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;
238 break;
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);
260 mm_log (tmp,NIL);
262 stream->ibuf = (char *) fs_get (stream->bufsize);
263 stream->obuf = (char *) fs_get (stream->bufsize);
264 return stream;
265 default:
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);
279 mm_log (tmp,ERROR);
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,
284 sizeof (SSLSTREAM));
285 break;
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);
292 mm_log (tmp,ERROR);
294 break;
296 return stream;
299 /* Generate error text from SSL error code
300 * Accepts: SSL status
301 * scratch buffer
302 * Returns: text if error status, else NIL
305 static char *ssl_analyze_status (SECURITY_STATUS err,char *buf)
307 switch (err) {
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:
312 return NIL;
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);
327 return buf;
330 /* SSL receive line
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;
344 stc->text.size = n;
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;
350 stc->text.size = n;
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);
356 ret[n] = '\0';
358 mail_free_stringlist (&stl);/* either way, done with list */
360 return ret;
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,
371 long *contd)
373 unsigned long n;
374 char *s,*ret,c,d;
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 */
384 return ret;
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 */
394 stream->ictr--;
395 ret[*size = --n] = '\0'; /* tie off string with null */
397 else *contd = LONGT; /* continuation needed */
398 return ret;
401 /* SSL receive buffer
402 * Accepts: SSL stream
403 * size in bytes
404 * buffer to read into
405 * Returns: T if success, NIL otherwise
408 long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
410 unsigned long n;
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 */
414 /* do the copy */
415 memcpy (buffer,stream->iptr,n);
416 buffer += n; /* update pointer */
417 stream->iptr += n;
418 size -= n; /* update # of bytes to do */
419 stream->ictr -= n;
421 buffer[0] = '\0'; /* tie off string */
422 return T;
425 /* SSL receive data
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;
434 SecBuffer buf[4];
435 SecBufferDesc msg;
436 size_t i;
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");
450 /* do the copy */
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 =
461 SECBUFFER_EMPTY;
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);
468 switch (status) {
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;
484 break;
485 default: /* anything else means we've lost */
486 return ssl_abort (stream);
489 return LONGT;
492 /* SSL send string as record
493 * Accepts: SSL stream
494 * string pointer
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));
504 /* SSL send string
505 * Accepts: SSL stream
506 * string pointer
507 * byte count
508 * Returns: T if success else NIL
511 long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
513 SecBuffer buf[4];
514 SecBufferDesc msg;
515 char *s = stream->ibuf;
516 size_t n = 0;
517 while (size) { /* until satisfied request */
518 /* header */
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));
526 /* trailer */
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);
530 /* spare */
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 */
544 return LONGT;
547 /* SSL close
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 */
558 /* SSL abort 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);
573 return NIL;
576 /* SSL get host name
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 */