imapext-2007

view src/osdep/nt/ssl_old.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children
line source
1 /* ========================================================================
2 * Copyright 1988-2008 University of Washington
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *
11 * ========================================================================
12 */
14 /*
15 * Program: SSL authentication/encryption module for Windows 9x and NT
16 *
17 * Author: Mark Crispin
18 * Networks and Distributed Computing
19 * Computing & Communications
20 * University of Washington
21 * Administration Building, AG-44
22 * Seattle, WA 98195
23 * Internet: MRC@CAC.Washington.EDU
24 *
25 * Date: 22 September 1998
26 * Last Edited: 13 January 2008
27 */
29 #define SECURITY_WIN32
30 #include <sspi.h>
31 #if(_WIN32_WINNT < 0x0400)
32 typedef unsigned int ALG_ID;
33 #else
34 #include <wincrypt.h>
35 ALGIDDEF
36 #endif
37 #include <schnlsp.h>
38 #include <issperr.h>
40 /* in case a binary runs on Windows 2000 */
41 #ifndef ISC_REQ_MANUAL_CRED_VALIDATION
42 #define ISC_REQ_MANUAL_CRED_VALIDATION 0x00080000
43 #endif
44 #ifndef SEC_E_UNTRUSTED_ROOT
45 #define SEC_E_UNTRUSTED_ROOT ((HRESULT) 0x80090325L)
46 #endif
47 #ifndef SEC_E_CERT_EXPIRED
48 #define SEC_E_CERT_EXPIRED ((HRESULT) 0x80090328L)
49 #endif
52 #define SSLBUFLEN 8192
55 /* SSL I/O stream */
57 typedef struct ssl_stream {
58 TCPSTREAM *tcpstream; /* TCP stream */
59 CredHandle cred; /* SSL credentials */
60 CtxtHandle context; /* SSL context */
61 /* stream encryption sizes */
62 SecPkgContext_StreamSizes sizes;
63 size_t bufsize;
64 int ictr; /* input counter */
65 char *iptr; /* input pointer */
66 int iextractr; /* extra input counter */
67 char *iextraptr; /* extra input pointer */
68 char *ibuf; /* input buffer */
69 char *obuf; /* output buffer */
70 } SSLSTREAM;
72 #include "sslio.h"
75 /* Function prototypes */
77 static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags);
78 static char *ssl_analyze_status (SECURITY_STATUS err,char *buf);
79 static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
80 long *contd);
81 static long ssl_abort (SSLSTREAM *stream);
83 /* Secure Sockets Layer network driver dispatch */
85 static struct ssl_driver ssldriver = {
86 ssl_open, /* open connection */
87 ssl_aopen, /* open preauthenticated connection */
88 ssl_getline, /* get a line */
89 ssl_getbuffer, /* get a buffer */
90 ssl_soutr, /* output pushed data */
91 ssl_sout, /* output string */
92 ssl_close, /* close connection */
93 ssl_host, /* return host name */
94 ssl_remotehost, /* return remote host name */
95 ssl_port, /* return port number */
96 ssl_localhost /* return local host name */
97 };
99 /* security function table */
100 static SecurityFunctionTable *sft = NIL;
101 static unsigned long ssltsz = 0;/* SSL maximum token length */
103 /* One-time SSL initialization */
105 static int sslonceonly = 0;
107 void ssl_onceonlyinit (void)
108 {
109 if (!sslonceonly++) { /* only need to call it once */
110 HINSTANCE lib;
111 FARPROC pi;
112 ULONG np;
113 SecPkgInfo *pp;
114 int i;
115 /* get security library */
116 if (((lib = LoadLibrary ("schannel.dll")) ||
117 (lib = LoadLibrary ("security.dll"))) &&
118 (pi = GetProcAddress (lib,SECURITY_ENTRYPOINT)) &&
119 (sft = (SecurityFunctionTable *) pi ()) &&
120 !(sft->EnumerateSecurityPackages (&np,&pp))) {
121 /* look for an SSL package */
122 for (i = 0; (i < (int) np); i++) if (!strcmp (pp[i].Name,UNISP_NAME)) {
123 /* note maximum token size and name */
124 ssltsz = pp[i].cbMaxToken;
125 /* apply runtime linkage */
126 mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver);
127 mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start);
128 return; /* all done */
129 }
130 }
131 }
132 }
134 /* SSL open
135 * Accepts: host name
136 * contact service name
137 * contact port number
138 * Returns: SSL stream if success else NIL
139 */
141 SSLSTREAM *ssl_open (char *host,char *service,unsigned long port)
142 {
143 TCPSTREAM *stream = tcp_open (host,service,port);
144 return stream ? ssl_start (stream,host,port) : NIL;
145 }
148 /* SSL authenticated open
149 * Accepts: host name
150 * service name
151 * returned user name buffer
152 * Returns: SSL stream if success else NIL
153 */
155 SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf)
156 {
157 return NIL; /* don't use this mechanism with SSL */
158 }
160 /* Start SSL/TLS negotiations
161 * Accepts: open TCP stream of session
162 * user's host name
163 * flags
164 * Returns: SSL stream if success else NIL
165 */
167 static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
168 {
169 SECURITY_STATUS e;
170 ULONG a;
171 TimeStamp t;
172 SecBuffer ibuf[2],obuf[1];
173 SecBufferDesc ibufs,obufs;
174 char tmp[MAILTMPLEN];
175 char *reason = NIL;
176 ULONG req = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT |
177 ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY |
178 ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR +
179 ((flags & NET_NOVALIDATECERT) ? ISC_REQ_MANUAL_CRED_VALIDATION :
180 ISC_REQ_MUTUAL_AUTH);
181 SCHANNEL_CRED tlscred;
182 char *buf = (char *) fs_get (ssltsz);
183 unsigned long size = 0;
184 sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL);
185 SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
186 sizeof (SSLSTREAM));
187 stream->tcpstream = tstream; /* bind TCP stream */
188 /* initialize TLS credential */
189 memset (&tlscred,0,sizeof (SCHANNEL_CRED));
190 tlscred.dwVersion = SCHANNEL_CRED_VERSION;
191 tlscred.grbitEnabledProtocols = SP_PROT_TLS1;
193 /* acquire credentials */
194 if (sft->AcquireCredentialsHandle
195 (NIL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NIL,(flags & NET_TLSCLIENT) ?
196 &tlscred : NIL,NIL,NIL,&stream->cred,&t)
197 != SEC_E_OK) reason = "Acquire credentials handle failed";
198 else while (!reason) { /* negotiate security context */
199 /* initialize buffers */
200 ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = buf;
201 ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NIL;
202 obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NIL;
203 ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
204 ibuf[1].BufferType = SECBUFFER_EMPTY;
205 /* initialize buffer descriptors */
206 ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION;
207 ibufs.cBuffers = 2; obufs.cBuffers = 1;
208 ibufs.pBuffers = ibuf; obufs.pBuffers = obuf;
209 /* negotiate security */
210 e = sft->InitializeSecurityContext
211 (&stream->cred,size ? &stream->context : NIL,host,req,0,
212 SECURITY_NETWORK_DREP,size? &ibufs:NIL,0,&stream->context,&obufs,&a,&t);
213 /* have an output buffer we need to send? */
214 if (obuf[0].pvBuffer && obuf[0].cbBuffer) {
215 if (!tcp_sout (stream->tcpstream,obuf[0].pvBuffer,obuf[0].cbBuffer))
216 reason = "Unexpected TCP output disconnect";
217 /* free the buffer */
218 sft->FreeContextBuffer (obuf[0].pvBuffer);
219 }
220 if (!reason) switch (e) { /* negotiation state */
221 case SEC_I_INCOMPLETE_CREDENTIALS:
222 break; /* server wants client auth */
223 case SEC_I_CONTINUE_NEEDED:
224 if (size) { /* continue, read any data? */
225 /* yes, anything regurgiated back to us? */
226 if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
227 /* yes, set this as the new data */
228 memmove (buf,buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
229 size = ibuf[1].cbBuffer;
230 break;
231 }
232 size = 0; /* otherwise, read more stuff from server */
233 }
234 case SEC_E_INCOMPLETE_MESSAGE:
235 /* need to read more data from server */
236 if (!tcp_getdata (stream->tcpstream))
237 reason = "Unexpected TCP input disconnect";
238 else {
239 memcpy (buf+size,stream->tcpstream->iptr,stream->tcpstream->ictr);
240 size += stream->tcpstream->ictr;
241 /* empty it from TCP's buffers */
242 stream->tcpstream->iptr += stream->tcpstream->ictr;
243 stream->tcpstream->ictr = 0;
244 }
245 break;
247 case SEC_E_OK: /* success, any data to be regurgitated? */
248 if (ibuf[1].BufferType == SECBUFFER_EXTRA) {
249 /* yes, set this as the new data */
250 memmove (stream->tcpstream->iptr = stream->tcpstream->ibuf,
251 buf + size - ibuf[1].cbBuffer,ibuf[1].cbBuffer);
252 stream->tcpstream->ictr = ibuf[1].cbBuffer;
253 }
254 if (reason = ssl_analyze_status
255 (sft->QueryContextAttributes
256 (&stream->context,SECPKG_ATTR_STREAM_SIZES,&stream->sizes),buf))
257 break; /* error getting sizes */
258 fs_give ((void **) &buf); /* flush temporary buffer */
259 /* make maximum-sized buffers */
260 stream->bufsize = stream->sizes.cbHeader +
261 stream->sizes.cbMaximumMessage + stream->sizes.cbTrailer;
262 if (stream->sizes.cbMaximumMessage < SSLBUFLEN)
263 fatal ("cbMaximumMessage is less than SSLBUFLEN!");
264 else if (stream->sizes.cbMaximumMessage < 16384) {
265 sprintf (tmp,"WINDOWS BUG: cbMaximumMessage = %ld, should be 16384",
266 (long) stream->sizes.cbMaximumMessage);
267 mm_log (tmp,NIL);
268 }
269 stream->ibuf = (char *) fs_get (stream->bufsize);
270 stream->obuf = (char *) fs_get (stream->bufsize);
271 return stream;
272 default:
273 reason = ssl_analyze_status (e,buf);
274 }
275 }
276 ssl_close (stream); /* failed to do SSL */
277 stream = NIL; /* no stream returned */
278 fs_give ((void **) &buf); /* flush temporary buffer */
279 switch (*reason) { /* analyze reason */
280 case '*': /* certificate failure */
281 ++reason; /* skip over certificate failure indication */
282 /* pass to error callback */
283 if (sf) (*sf) (host,reason,flags);
284 else { /* no error callback, build error message */
285 sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason);
286 mm_log (tmp,ERROR);
287 }
288 case '\0': /* user answered no to certificate callback */
289 if (flags & NET_TRYSSL) /* return dummy stream to stop tryssl */
290 stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
291 sizeof (SSLSTREAM));
292 break;
293 default: /* non-certificate failure */
294 if (flags & NET_TRYSSL); /* no error output if tryssl */
295 /* pass to error callback */
296 else if (sf) (*sf) (host,reason,flags);
297 else { /* no error callback, build error message */
298 sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason);
299 mm_log (tmp,ERROR);
300 }
301 break;
302 }
303 return stream;
304 }
306 /* Generate error text from SSL error code
307 * Accepts: SSL status
308 * scratch buffer
309 * Returns: text if error status, else NIL
310 */
312 static char *ssl_analyze_status (SECURITY_STATUS err,char *buf)
313 {
314 switch (err) {
315 case SEC_E_OK: /* no error */
316 case SEC_I_CONTINUE_NEEDED:
317 case SEC_I_INCOMPLETE_CREDENTIALS:
318 case SEC_E_INCOMPLETE_MESSAGE:
319 return NIL;
320 case SEC_E_NO_AUTHENTICATING_AUTHORITY:
321 return "*No authority could be contacted for authentication";
322 case SEC_E_WRONG_PRINCIPAL:
323 return "*Server name does not match certificate";
324 case SEC_E_UNTRUSTED_ROOT:
325 return "*Self-signed certificate or untrusted authority";
326 case SEC_E_CERT_EXPIRED:
327 return "*Certificate has expired";
328 case SEC_E_INVALID_TOKEN:
329 return "Invalid token, probably not an SSL server";
330 case SEC_E_UNSUPPORTED_FUNCTION:
331 return "SSL not supported on this machine - upgrade your system software";
332 }
333 sprintf (buf,"Unexpected SChannel error %lx - report this",err);
334 return buf;
335 }
337 /* SSL receive line
338 * Accepts: SSL stream
339 * Returns: text line string or NIL if failure
340 */
342 char *ssl_getline (SSLSTREAM *stream)
343 {
344 unsigned long n,contd;
345 char *ret = ssl_getline_work (stream,&n,&contd);
346 if (ret && contd) { /* got a line needing continuation? */
347 STRINGLIST *stl = mail_newstringlist ();
348 STRINGLIST *stc = stl;
349 do { /* collect additional lines */
350 stc->text.data = (unsigned char *) ret;
351 stc->text.size = n;
352 stc = stc->next = mail_newstringlist ();
353 ret = ssl_getline_work (stream,&n,&contd);
354 } while (ret && contd);
355 if (ret) { /* stash final part of line on list */
356 stc->text.data = (unsigned char *) ret;
357 stc->text.size = n;
358 /* determine how large a buffer we need */
359 for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
360 ret = fs_get (n + 1); /* copy parts into buffer */
361 for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
362 memcpy (ret + n,stc->text.data,stc->text.size);
363 ret[n] = '\0';
364 }
365 mail_free_stringlist (&stl);/* either way, done with list */
366 }
367 return ret;
368 }
370 /* SSL receive line or partial line
371 * Accepts: SSL stream
372 * pointer to return size
373 * pointer to return continuation flag
374 * Returns: text line string, size and continuation flag, or NIL if failure
375 */
377 static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
378 long *contd)
379 {
380 unsigned long n;
381 char *s,*ret,c,d;
382 *contd = NIL; /* assume no continuation */
383 /* make sure have data */
384 if (!ssl_getdata (stream)) return NIL;
385 for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
386 d = *stream->iptr++; /* slurp another character */
387 if ((c == '\015') && (d == '\012')) {
388 ret = (char *) fs_get (n--);
389 memcpy (ret,s,*size = n); /* copy into a free storage string */
390 ret[n] = '\0'; /* tie off string with null */
391 return ret;
392 }
393 }
394 /* copy partial string from buffer */
395 memcpy ((ret = (char *) fs_get (n)),s,*size = n);
396 /* get more data from the net */
397 if (!ssl_getdata (stream)) fs_give ((void **) &ret);
398 /* special case of newline broken by buffer */
399 else if ((c == '\015') && (*stream->iptr == '\012')) {
400 stream->iptr++; /* eat the line feed */
401 stream->ictr--;
402 ret[*size = --n] = '\0'; /* tie off string with null */
403 }
404 else *contd = LONGT; /* continuation needed */
405 return ret;
406 }
408 /* SSL receive buffer
409 * Accepts: SSL stream
410 * size in bytes
411 * buffer to read into
412 * Returns: T if success, NIL otherwise
413 */
415 long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
416 {
417 unsigned long n;
418 while (size > 0) { /* until request satisfied */
419 if (!ssl_getdata (stream)) return NIL;
420 n = min (size,stream->ictr);/* number of bytes to transfer */
421 /* do the copy */
422 memcpy (buffer,stream->iptr,n);
423 buffer += n; /* update pointer */
424 stream->iptr += n;
425 size -= n; /* update # of bytes to do */
426 stream->ictr -= n;
427 }
428 buffer[0] = '\0'; /* tie off string */
429 return T;
430 }
432 /* SSL receive data
433 * Accepts: TCP/IP stream
434 * Returns: T if success, NIL otherwise
435 */
437 long ssl_getdata (SSLSTREAM *stream)
438 {
439 while (stream->ictr < 1) { /* decrypted buffer empty? */
440 SECURITY_STATUS status;
441 SecBuffer buf[4];
442 SecBufferDesc msg;
443 size_t i;
444 size_t n = 0; /* initially no bytes to decrypt */
445 do { /* yes, make sure have data from TCP */
446 if (stream->iextractr) { /* have previous unread data? */
447 memcpy (stream->ibuf + n,stream->iextraptr,stream->iextractr);
448 n += stream->iextractr; /* update number of bytes read */
449 stream->iextractr = 0; /* no more extra data */
450 }
451 else { /* read from TCP */
452 if (!tcp_getdata (stream->tcpstream)) return ssl_abort (stream);
453 /* maximum amount of data to copy */
454 if (!(i = min (stream->bufsize - n,stream->tcpstream->ictr)))
455 fatal ("incomplete SecBuffer exceeds maximum buffer size");
456 /* do the copy */
457 memcpy (stream->ibuf + n,stream->tcpstream->iptr,i);
458 stream->tcpstream->iptr += i;
459 stream->tcpstream->ictr -= i;
460 n += i; /* update number of bytes to decrypt */
461 }
462 buf[0].cbBuffer = n; /* first SecBuffer gets data */
463 buf[0].pvBuffer = stream->ibuf;
464 buf[0].BufferType = SECBUFFER_DATA;
465 /* subsequent ones are for spares */
466 buf[1].BufferType = buf[2].BufferType = buf[3].BufferType =
467 SECBUFFER_EMPTY;
468 msg.ulVersion = SECBUFFER_VERSION;
469 msg.cBuffers = 4; /* number of SecBuffers */
470 msg.pBuffers = buf; /* first SecBuffer */
472 } while ((status = ((DECRYPT_MESSAGE_FN) sft->Reserved4)
473 (&stream->context,&msg,0,NIL)) == SEC_E_INCOMPLETE_MESSAGE);
474 switch (status) {
475 case SEC_E_OK: /* won */
476 case SEC_I_RENEGOTIATE: /* won but lost it after this buffer */
477 /* hunt for a buffer */
478 for (i = 0; (i < 4) && (buf[i].BufferType != SECBUFFER_DATA) ; i++);
479 if (i < 4) { /* found a buffer? */
480 /* yes, set up pointer and counter */
481 stream->iptr = buf[i].pvBuffer;
482 stream->ictr = buf[i].cbBuffer;
483 /* any unprocessed data? */
484 while (++i < 4) if (buf[i].BufferType == SECBUFFER_EXTRA) {
485 /* yes, note for next time around */
486 stream->iextraptr = buf[i].pvBuffer;
487 stream->iextractr = buf[i].cbBuffer;
488 }
489 }
490 break;
491 default: /* anything else means we've lost */
492 return ssl_abort (stream);
493 }
494 }
495 return LONGT;
496 }
498 /* SSL send string as record
499 * Accepts: SSL stream
500 * string pointer
501 * Returns: T if success else NIL
502 */
504 long ssl_soutr (SSLSTREAM *stream,char *string)
505 {
506 return ssl_sout (stream,string,(unsigned long) strlen (string));
507 }
510 /* SSL send string
511 * Accepts: SSL stream
512 * string pointer
513 * byte count
514 * Returns: T if success else NIL
515 */
517 long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
518 {
519 SecBuffer buf[4];
520 SecBufferDesc msg;
521 char *s = stream->ibuf;
522 size_t n = 0;
523 while (size) { /* until satisfied request */
524 /* header */
525 buf[0].BufferType = SECBUFFER_STREAM_HEADER;
526 memset (buf[0].pvBuffer = stream->obuf,0,
527 buf[0].cbBuffer = stream->sizes.cbHeader);
528 /* message (up to maximum size) */
529 buf[1].BufferType = SECBUFFER_DATA;
530 memcpy (buf[1].pvBuffer = stream->obuf + stream->sizes.cbHeader,string,
531 buf[1].cbBuffer = min (size,SSLBUFLEN));
532 /* trailer */
533 buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
534 memset (buf[2].pvBuffer = ((char *) buf[1].pvBuffer) + buf[1].cbBuffer,0,
535 buf[2].cbBuffer = stream->sizes.cbTrailer);
536 /* spare */
537 buf[3].BufferType = SECBUFFER_EMPTY;
538 msg.ulVersion = SECBUFFER_VERSION;
539 msg.cBuffers = 4; /* number of SecBuffers */
540 msg.pBuffers = buf; /* first SecBuffer */
541 string += buf[1].cbBuffer;
542 size -= buf[1].cbBuffer; /* this many bytes processed */
543 /* encrypt and send message */
544 if ((((ENCRYPT_MESSAGE_FN) sft->Reserved3)
545 (&stream->context,0,&msg,NIL) != SEC_E_OK) ||
546 !tcp_sout (stream->tcpstream,stream->obuf,
547 buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer))
548 return ssl_abort (stream);/* encryption or sending failed */
549 }
550 return LONGT;
551 }
553 /* SSL close
554 * Accepts: SSL stream
555 */
557 void ssl_close (SSLSTREAM *stream)
558 {
559 ssl_abort (stream); /* nuke the stream */
560 fs_give ((void **) &stream); /* flush the stream */
561 }
564 /* SSL abort stream
565 * Accepts: SSL stream
566 * Returns: NIL always
567 */
569 static long ssl_abort (SSLSTREAM *stream)
570 {
571 if (stream->tcpstream) { /* close TCP stream */
572 sft->DeleteSecurityContext (&stream->context);
573 sft->FreeCredentialHandle (&stream->cred);
574 tcp_close (stream->tcpstream);
575 stream->tcpstream = NIL;
576 }
577 if (stream->ibuf) fs_give ((void **) &stream->ibuf);
578 if (stream->obuf) fs_give ((void **) &stream->obuf);
579 return NIL;
580 }
582 /* SSL get host name
583 * Accepts: SSL stream
584 * Returns: host name for this stream
585 */
587 char *ssl_host (SSLSTREAM *stream)
588 {
589 return tcp_host (stream->tcpstream);
590 }
593 /* SSL get remote host name
594 * Accepts: SSL stream
595 * Returns: host name for this stream
596 */
598 char *ssl_remotehost (SSLSTREAM *stream)
599 {
600 return tcp_remotehost (stream->tcpstream);
601 }
604 /* SSL return port for this stream
605 * Accepts: SSL stream
606 * Returns: port number for this stream
607 */
609 unsigned long ssl_port (SSLSTREAM *stream)
610 {
611 return tcp_port (stream->tcpstream);
612 }
615 /* SSL get local host name
616 * Accepts: SSL stream
617 * Returns: local host name
618 */
620 char *ssl_localhost (SSLSTREAM *stream)
621 {
622 return tcp_localhost (stream->tcpstream);
623 }
625 #include "ssl_none.c" /* currently no server support */

UW-IMAP'd extensions by yuuji