aboutsummaryrefslogtreecommitdiff
path: root/openssl/trunk/demos/easy_tls/easy-tls.c
blob: 9cd8314c3e4f92e32dcff5ca9db35580c03882e3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* crypto/asn1/a_bytes.c */
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
 * All rights reserved.
 *
 * This package is an SSL implementation written
 * by Eric Young (eay@cryptsoft.com).
 * The implementation was written so as to conform with Netscapes SSL.
 * 
 * This library is free for commercial and non-commercial use as long as
 * the following conditions are aheared to.  The following conditions
 * apply to all code found in this distribution, be it the RC4, RSA,
 * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
 * included with this distribution is covered by the same copyright terms
 * except that the holder is Tim Hudson (tjh@cryptsoft.com).
 * 
 * Copyright remains Eric Young's, and as such any Copyright notices in
 * the code are not to be removed.
 * If this package is used in a product, Eric Young should be given attribution
 * as the author of the parts of the library used.
 * This can be in the form of a textual message at program startup or
 * in documentation (online or textual) provided with the package.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    "This product includes cryptographic software written by
 *     Eric Young (eay@cryptsoft.com)"
 *    The word 'cryptographic' can be left out if the rouines from the library
 *    being used are not cryptographic related :-).
 * 4. If you include any Windows specific code (or a derivative thereof) from 
 *    the apps directory (application code) you must include an acknowledgement:
 *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
 * 
 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 * The licence and distribution terms for any publically available version or
 * derivative of this code cannot be changed.  i.e. this code cannot simply be
 * copied and put under another distribution licence
 * [including the GNU Public Licence.]
 */

#include <stdio.h>
#include "cryptlib.h"
#include <openssl/asn1.h>

static int asn1_collate_primitive(ASN1_STRING *a, ASN1_const_CTX *c);
/* type is a 'bitmap' of acceptable string types.
 */
ASN1_STRING *d2i_ASN1_type_bytes(ASN1_STRING **a, const unsigned char **pp,
	     long length, int type)
	{
	ASN1_STRING *ret=NULL;
	const unsigned char *p;
	unsigned char *s;
	long len;
	int inf,tag,xclass;
	int i=0;

	p= *pp;
	inf=ASN1_get_object(&p,&len,&tag,&xclass,length);
	if (inf & 0x80) goto err;

	if (tag >= 32)
		{
		i=ASN1_R_TAG_VALUE_TOO_HIGH;;
		goto err;
		}
	if (!(ASN1_tag2bit(tag) & type))
		{
		i=ASN1_R_WRONG_TYPE;
		goto err;
		}

	/* If a bit-string, exit early */
	if (tag == V_ASN1_BIT_STRING)
		return(d2i_ASN1_BIT_STRING(a,pp,length));

	if ((a == NULL) || ((*a) == NULL))
		{
		if ((ret=ASN1_STRING_new()) == NULL) return(NULL);
		}
	else
		ret=(*a);

	if (len != 0)
		{
		s=(unsigned char *)OPENSSL_malloc((int)len+1);
		if (s == NULL)
			{
			i=ERR_R_MALLOC_FAILURE;
			goto err;
			}
		memcpy(s,p,(int)len);
		s[len]='\0';
		p+=len;
		}
	else
		s=NULL;

	if (ret->data != NULL) OPENSSL_free(ret->data);
	ret->length=(int)len;
	ret->data=s;
	ret->type=tag;
	if (a != NULL) (*a)=ret;
	*pp=p;
	return(ret);
err:
	ASN1err(ASN1_F_D2I_ASN1_TYPE_BYTES,i);
	if ((ret != NULL) && ((a == NULL) || (*a != ret)))
		ASN1_STRING_free(ret);
	return(NULL);
	}

int i2d_ASN1_bytes(ASN1_STRING *a, unsigned char **pp, int tag, int xclass)
	{
	int ret,r,constructed;
	unsigned char *p;

	if (a == NULL)  return(0);

	if (tag == V_ASN1_BIT_STRING)
		return(i2d_ASN1_BIT_STRING(a,pp));
		
	ret=a->length;
	r=ASN1_object_size(0,ret,tag);
	if (pp == NULL) return(r);
	p= *pp;

	if ((tag == V_ASN1_SEQUENCE) || (tag == V_ASN1_SET))
		constructed=1;
	else
		constructed=0;
	ASN1_put_object(&p,constructed,ret,tag,xclass);
	memcpy(p,a->data,a->length);
	p+=a->length;
	*pp= p;
	return(r);
	}

ASN1_STRING *d2i_ASN1_bytes(ASN1_STRING **a, const unsigned char **pp,
	     long length, int Ptag, int Pclass)
	{
	ASN1_STRING *ret=NULL;
	const unsigned char *p;
	unsigned char *s;
	long len;
	int inf,tag,xclass;
	int i=0;

	if ((a == NULL) || ((*a) == NULL))
		{
		if ((ret=ASN1_STRING_new()) == NULL) return(NULL);
		}
	else
		ret=(*a);

	p= *pp;
	inf=ASN1_get_object(&p,&len,&tag,&xclass,length);
	if (inf & pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* -*- Mode: C; c-file-style: "bsd" -*- */
/*
 * easy-tls.c -- generic TLS proxy.
 * $Id: easy-tls.c,v 1.4 2002/03/05 09:07:16 bodo Exp $
 */
/*
 (c) Copyright 1999 Bodo Moeller.  All rights reserved.

 This is free software; you can redistributed and/or modify it
 unter the terms of either
   -  the GNU General Public License as published by the
      Free Software Foundation, version 1, or (at your option)
      any later version,
 or
   -  the following license:
*/
/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that each of the following
 * conditions is met:
 *
 * 1. Redistributions qualify as "freeware" or "Open Source Software" under
 *    one of the following terms:
 * 
 *    (a) Redistributions are made at no charge beyond the reasonable cost of
 *        materials and delivery.
 * 
 *    (b) Redistributions are accompanied by a copy of the Source Code
 *        or by an irrevocable offer to provide a copy of the Source Code
 *        for up to three years at the cost of materials and delivery.
 *        Such redistributions must allow further use, modification, and
 *        redistribution of the Source Code under substantially the same
 *        terms as this license.
 *
 * 2. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 3. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 4. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by Bodo Moeller."
 *    (If available, substitute umlauted o for oe.)
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Bodo Moeller."
 *
 * THIS SOFTWARE IS PROVIDED BY BODO MOELLER ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL BODO MOELLER OR
 * HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Attribution for OpenSSL library:
 *
 * This product includes cryptographic software written by Eric Young
 * (eay@cryptsoft.com).  This product includes software written by Tim
 * Hudson (tjh@cryptsoft.com).
 * This product includes software developed by the OpenSSL Project
 * for use in the OpenSSL Toolkit. (http://www.openssl.org/)
 */

static char const rcsid[] =
"$Id: easy-tls.c,v 1.4 2002/03/05 09:07:16 bodo Exp $";

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>

#include <openssl/crypto.h>
#include <openssl/dh.h>
#include <openssl/dsa.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/opensslv.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#ifndef NO_RSA
 #include <openssl/rsa.h>
#endif
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>

#if OPENSSL_VERSION_NUMBER < 0x00904000L /* 0.9.4-dev */
# error "This program needs OpenSSL 0.9.4 or later."
#endif

#include "easy-tls.h" /* include after <openssl/ssl.h> if both are needed */

#if TLS_INFO_SIZE > PIPE_BUF
# if PIPE_BUF < 512
#  error "PIPE_BUF < 512" /* non-POSIX */
# endif
# error "TLS_INFO_SIZE > PIPE_BUF"
#endif

/*****************************************************************************/

#ifdef TLS_APP
# include TLS_APP
#endif

/* Applications can define:
 *   TLS_APP_PROCESS_INIT -- void ...(int fd, int client_p, void *apparg)
 *   TLS_CUMULATE_ERRORS 
 *   TLS_ERROR_BUFSIZ
 *   TLS_APP_ERRFLUSH -- void ...(int child_p, char *, size_t, void *apparg)
 */

#ifndef TLS_APP_PROCESS_INIT
# define TLS_APP_PROCESS_INIT(fd, client_p, apparg) ((void) 0)
#endif

#ifndef TLS_ERROR_BUFSIZ
# define TLS_ERROR_BUFSIZ (10*160)
#endif
#if TLS_ERROR_BUFSIZ < 2 /* {'\n',0} */
# error "TLS_ERROR_BUFSIZE is too small."
#endif

#ifndef TLS_APP_ERRFLUSH
# define TLS_APP_ERRFLUSH tls_app_errflush
static void
tls_app_errflush(int child_p, char *errbuf, size_t num, void *apparg)
{
    fputs(errbuf, stderr);
}
#endif

/*****************************************************************************/

#ifdef DEBUG_TLS
# define DEBUG_MSG(x) fprintf(stderr,"  %s\n",x)
# define DEBUG_MSG2(x,y) fprintf(stderr, "  %s: %d\n",x,y)
static int tls_loop_count = 0;
static int tls_select_count = 0;
#else
# define DEBUG_MSG(x) (void)0
# define DEBUG_MSG2(x,y) (void)0
#endif

static void tls_rand_seed_uniquely(void);
static void tls_proxy(int clear_fd, int tls_fd, int info_fd, SSL_CTX *ctx, int client_p);
static int tls_socket_nonblocking(int fd);

static int tls_child_p = 0;
static void *tls_child_apparg;


struct tls_start_proxy_args
tls_start_proxy_defaultargs(void)
{
    struct tls_start_proxy_args ret;

    ret.fd = -1;
    ret.client_p = -1;
    ret.ctx = NULL;
    ret.pid = NULL;
    ret.infofd = NULL;
    
    return ret;
}

/* Slice in TLS proxy process at fd.
 * Return value:
 *   0    ok  (*pid is set to child's PID if pid != NULL),
 *   < 0  look at errno
 *   > 0  other error
 *   (return value encodes place of error)
 *
 */
int
tls_start_proxy(struct tls_start_proxy_args a, void *apparg)
{
    int fds[2] = {-1, -1};
    int infofds[2] = {-1, -1};
    int r, getfd, getfl;
    int ret;

    DEBUG_MSG2("tls_start_proxy fd", a.fd);
    DEBUG_MSG2("tls_start_proxy client_p", a.client_p);

    if (a.fd == -1 || a.client_p == -1 || a.ctx == NULL)
	return 1;

    if (a.pid != NULL) {
	*a.pid = 0;
    }
    if (a.infofd != NULL) {
	*a.infofd = -1;
    }

    r = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
    if (r == -1)
	return -1;
    if (a.fd >= FD_SETSIZE || fds[0] >= FD_SETSIZE) {
	ret = 2;
	goto err;
    }
    if (a.infofd != NULL) {
	r = pipe(infofds);
	if (r == -1) {
	    ret = -3;
	    goto err;
	}
    }

    r = fork();
    if (r == -1) {
	ret = -4;
	goto err;
    }
    if (r == 0) {
	DEBUG_MSG("fork");
	tls_child_p = 1;
	tls_child_apparg = apparg;
	close(fds[1]);
	if (infofds[0] != -1)
	    close(infofds[0]);
	TLS_APP_PROCESS_INIT(a.fd, a.client_p, apparg);
	DEBUG_MSG("TLS_APP_PROCESS_INIT");
	tls_proxy(fds[0], a.fd, infofds[1], a.ctx, a.client_p);
	exit(0);
    }
    if (a.pid != NULL)
	*a.pid = r;
    if (infofds[1] != -1) {
	close(infofds[1]);
	infofds[1] = -1;
    }
    /* install fds[1] in place of fd: */
    close(fds[0]);
    fds[0] = -1;
    getfd = fcntl(a.fd, F_GETFD);
    getfl = fcntl(a.fd, F_GETFL);
    r = dup2(fds[1], a.fd);
    close(fds[1]);
    fds[1] = -1;
    if (r == -1) {
	ret = -5;
	goto err;
    }
    if (getfd != 1)
	fcntl(a.fd, F_SETFD, getfd);
    if (getfl & O_NONBLOCK)
	(void)tls_socket_nonblocking(a.fd);
    if (a.infofd != NULL)
	*a.infofd = infofds[0];
    return 0;
    
  err:
    if (fds[0] != -1)
	close(fds[0]);
    if (fds[1] != -1)
	close(fds[1]);
    if (infofds[0] != -1)
	close(infofds[0]);
    if (infofds[1] != -1)
	close(infofds[1]);
    return ret;
}

/*****************************************************************************/

static char errbuf[TLS_ERROR_BUFSIZ];
static size_t errbuf_i = 0;

static void
tls_errflush(void *apparg)
{
    if (errbuf_i == 0)
	return;
    
    assert(errbuf_i < sizeof errbuf);
    assert(errbuf[errbuf_i] == 0);
    if (errbuf_i == sizeof errbuf - 1) {
	/* make sure we have a newline, even if string has been truncated */
	errbuf[errbuf_i - 1] = '\n';
    }

    /* TLS_APP_ERRFLUSH may modify the string as needed,
     * e.g. substitute other characters for \n for convenience */
    TLS_APP_ERRFLUSH(tls_child_p, errbuf, errbuf_i, apparg);

    errbuf_i = 0;
}

static void
tls_errprintf(int flush, void *apparg, const char *fmt, ...)
{
    va_list args;
    int r;
    
    if (errbuf_i < sizeof errbuf - 1) {
	size_t n;

	va_start(args, fmt);
	n = (sizeof errbuf) - errbuf_i;
	r = vsnprintf(errbuf + errbuf_i, n, fmt, args);
	if (r >= n)
	    r = n - 1;
	if (r >= 0) {
	    errbuf_i += r;
	} else {
	    errbuf_i = sizeof errbuf - 1;
	    errbuf[errbuf_i] = '\0';
	}
	assert(errbuf_i < sizeof errbuf);
	assert(errbuf[errbuf_i] == 0);
    }
#ifndef TLS_CUMULATE_ERRORS
    tls_errflush(apparg);
#else
    if (flush)
	tls_errflush(apparg);
#endif
}

/* app_prefix.. are for additional information provided by caller.
 * If OpenSSL error queue is empty, print default_text ("???" if NULL).
 */
static char *
tls_openssl_errors(const char *app_prefix_1, const char *app_prefix_2, const char *default_text, void *apparg)
{
    static char reasons[255];
    size_t reasons_i;
    unsigned long err;
    const char *file;
    int line;
    const char *data;
    int flags;
    char *errstring;
    int printed_something = 0;
    
    reasons_i = 0;

    assert(app_prefix_1 != NULL);
    assert(app_prefix_2 != NULL);

    if (default_text == NULL)
	default_text = "?""?""?";
    
    while ((err = ERR_get_error_line_data(&file,&line,&data,&flags)) != 0) {
	if (reasons_i < sizeof reasons) {
	    size_t n;
	    int r;

	    n = (sizeof reasons) - reasons_i;
	    r = snprintf(reasons + reasons_i, n, "%s%s", (reasons_i > 0 ? ", " : ""), ERR_reason_error_string(err));
	    if (r >= n)
		r = n - 1;
	    if (r >= 0) {
		reasons_i += r;
	    } else {
		reasons_i = sizeof reasons;
	    }
	    assert(reasons_i <= sizeof reasons);
	}
	
	errstring = ERR_error_string(err, NULL);
	assert(errstring != NULL);
	tls_errprintf(0, apparg, "OpenSSL error%s%s: %s:%s:%d:%s\n", app_prefix_1, app_prefix_2, errstring, file, line, (flags & ERR_TXT_STRING) ? data : "");
	printed_something = 1;
    }

    if (!printed_something) {
	assert(reasons_i == 0);
	snprintf(reasons, sizeof reasons, "%s", default_text);
	tls_errprintf(0, apparg, "OpenSSL error%s%s: %s\n", app_prefix_1, app_prefix_2, default_text);
    }

#ifdef TLS_CUMULATE_ERRORS    
    tls_errflush(apparg);
#endif
    assert(errbuf_i == 0);

    return reasons;
}

/*****************************************************************************/

static int tls_init_done = 0;

static int
tls_init(void *apparg)
{
    if (tls_init_done)
	return 0;
    
    SSL_load_error_strings();
    if (!SSL_library_init() /* aka SSLeay_add_ssl_algorithms() */ ) {
	tls_errprintf(1, apparg, "SSL_library_init failed.\n");
	return -1;
    }
    tls_init_done = 1;
    tls_rand_seed();
    return 0;
}

/*****************************************************************************/

static void
tls_rand_seed_uniquely(void)
{
    struct {
	pid_t pid;
	time_t time;
	void *stack;
    } data;

    data.pid = getpid();
    data.time = time(NULL);
    data.stack = (void *)&data;

    RAND_seed((const void *)&data, sizeof data);
}

void
tls_rand_seed(void)
{
    struct {
	struct utsname uname;
	int uname_1;
	int uname_2;
	uid_t uid;
	uid_t euid;
	gid_t gid;
	gid_t egid;
    } data;
    
    data.uname_1 = uname(&data.uname);
    data.uname_2 = errno; /* Let's hope that uname fails randomly :-) */

    data.uid = getuid();
    data.euid = geteuid();
    data.gid = getgid();
    data.egid = getegid();
    
    RAND_seed((const void *)&data, sizeof data);
    tls_rand_seed_uniquely();
}

static int tls_rand_seeded_p = 0;

#define my_MIN_SEED_BYTES 256 /* struct stat can be larger than 128 */
int
tls_rand_seed_from_file(const char *filename, size_t n, void *apparg)
{
    /* Seed OpenSSL's random number generator from file.
       Try to read n bytes if n > 0, whole file if n == 0. */

    int r;

    if (tls_init(apparg) == -1)
	return -1;
    tls_rand_seed();

    r = RAND_load_file(filename, (n > 0 && n < LONG_MAX) ? (long)n : LONG_MAX);
    /* r is the number of bytes filled into the random number generator,
     * which are taken from "stat(filename, ...)" in addition to the
     * file contents.
     */
    assert(1 < my_MIN_SEED_BYTES);
    /* We need to detect at least those cases when the file does not exist
     * at all.  With current versions of OpenSSL, this should do it: */
    if (n == 0)
	n = my_MIN_SEED_BYTES;
    if (r < n) {
	tls_errprintf(1, apparg, "rand_seed_from_file: could not read %d bytes from %s.\n", n, filename);
	return -1;
    } else {
	tls_rand_seeded_p = 1;
	return 0;
    }
}

void
tls_rand_seed_from_memory(const void *buf, size_t n)
{
    size_t i = 0;
    
    while (i < n) {
	size_t rest = n - i;
	int chunk = rest < INT_MAX ? (int)rest : INT_MAX;
	RAND_seed((const char *)buf + i, chunk);
	i += chunk;
    }
    tls_rand_seeded_p = 1;
}


/*****************************************************************************/

struct tls_x509_name_string {
    char str[100];
};

static void
tls_get_x509_subject_name_oneline(X509 *cert, struct tls_x509_name_string *namestring)
{
    X509_NAME *name;

    if (cert == NULL) {
	namestring->str[0] = '\0';
	return;
    }
    
    name = X509_get_subject_name(cert); /* does not increment any reference counter */

    assert(sizeof namestring->str >= 4); /* "?" or "...", plus 0 */
    
    if (name == NULL) {
	namestring->str[0] = '?';
	namestring->str[1] = 0;
    } else {
	size_t len;

	X509_NAME_oneline(name, namestring->str, sizeof namestring->str);
	len = strlen(namestring->str);
	assert(namestring->str[len] == 0);
	assert(len < sizeof namestring->str);

	if (len+1 == sizeof namestring->str) {
	    /* (Probably something was cut off.)
	     * Does not really work -- X509_NAME_oneline truncates after
	     * name components, we cannot tell from the result whether
	     * anything is missing. */

	    assert(namestring->str[len] == 0);
	    namestring->str[--len] = '.';
	    namestring->str[--len] = '.';
	    namestring->str[--len] = '.';
	}
    }
}

/*****************************************************************************/

/* to hinder OpenSSL from asking for passphrases */
static int
no_passphrase_callback(char *buf, int num, int w, void *arg)
{
    return -1;
}

#if OPENSSL_VERSION_NUMBER >= 0x00907000L
static int
verify_dont_fail_cb(X509_STORE_CTX *c, void *unused_arg)
#else
static int
verify_dont_fail_cb(X509_STORE_CTX *c)
#endif
{
    int i;
    
    i = X509_verify_cert(c); /* sets c->error */
#if OPENSSL_VERSION_NUMBER >= 0x00905000L /* don't allow unverified
					   * certificates -- they could
					   * survive session reuse, but
					   * OpenSSL < 0.9.5-dev does not
					   * preserve their verify_result */
    if (i == 0)
	return 1;
    else
#endif
	return i;
}

static DH *tls_dhe1024 = NULL; /* generating these takes a while, so do it just once */

void
tls_set_dhe1024(int i, void *apparg)
{
    DSA *dsaparams;
    DH *dhparams;
    const char *seed[] = { ";-)  :-(  :-)  :-(  ",
			   ";-)  :-(  :-)  :-(  ",
			   "Random String no. 12",
			   ";-)  :-(  :-)  :-(  ",
			   "hackers have even mo", /* from jargon file */
    };
    unsigned char seedbuf[20];
    
    tls_init(apparg);
    if (i >= 0) {
	i %= sizeof seed / sizeof seed[0];
	assert(strlen(seed[i]) == 20);
	memcpy(seedbuf, seed[i], 20);
	dsaparams = DSA_generate_parameters(1024, seedbuf, 20, NULL, NULL, 0, NULL);
    } else {
	/* random parameters (may take a while) */
	dsaparams = DSA_generate_parameters(1024, NULL, 0, NULL, NULL, 0, NULL);
    }
    
    if (dsaparams == NULL) {
	tls_openssl_errors("", "", NULL, apparg);
	return;
    }
    dhparams = DSA_dup_DH(dsaparams);
    DSA_free(dsaparams);
    if (dhparams == NULL) {
	tls_openssl_errors("", "", NULL, apparg);
	return;
    }
    if (tls_dhe1024 != NULL)
	DH_free(tls_dhe1024);
    tls_dhe1024 = dhparams;
}

struct tls_create_ctx_args
tls_create_ctx_defaultargs(void)
{
	struct tls_create_ctx_args ret;

	ret.client_p = 0;
	ret.certificate_file = NULL;
	ret.key_file = NULL;
	ret.ca_file = NULL;
	ret.verify_depth = -1;
	ret.fail_unless_verified = 0;
	ret.export_p = 0;

	return ret;
}

SSL_CTX *
tls_create_ctx(struct tls_create_ctx_args a, void *apparg)
{
    int r;
    static long context_num = 0;
    SSL_CTX *ret;
    const char *err_pref_1 = "", *err_pref_2 = "";
    
    if (tls_init(apparg) == -1)
	return NULL;

    ret = SSL_CTX_new((a.client_p? SSLv23_client_method:SSLv23_server_method)());

    if (ret == NULL)
	goto err;

    SSL_CTX_set_default_passwd_cb(ret, no_passphrase_callback);
    SSL_CTX_set_mode(ret, SSL_MODE_ENABLE_PARTIAL_WRITE);
    
    if ((a.certificate_file != NULL) || (a.key_file != NULL)) {
	if (a.key_file == NULL) {
	    tls_errprintf(1, apparg, "Need a key file.\n");
	    goto err_return;
	}
	if (a.certificate_file == NULL) {
	    tls_errprintf(1, apparg, "Need a certificate chain file.\n");
	    goto err_return;
	}
	
	if (!SSL_CTX_use_PrivateKey_file(ret, a.key_file, SSL_FILETYPE_PEM))
	    goto err;
	if (!tls_rand_seeded_p) {
	    /* particularly paranoid people may not like this --
	     * so provide your own random seeding before calling this */
	    if (tls_rand_seed_from_file(a.key_file, 0, apparg) == -1)
		goto err_return;
	}
	if (!SSL_CTX_use_certificate_chain_file(ret, a.certificate_file))
	    goto err;
	if (!SSL_CTX_check_private_key(ret)) {
	    tls_errprintf(1, apparg, "Private key \"%s\" does not match certificate \"%s\".\n", a.key_file, a.certificate_file);
	    goto err_peek;
	}
    }
    
    if ((a.ca_file != NULL) || (a.verify_depth > 0)) {
	context_num++;
	r = SSL_CTX_set_session_id_context(ret, (const void *)&context_num, (unsigned int)sizeof context_num);
	if (!r)
	    goto err;
	
	SSL_CTX_set_verify(ret, SSL_VERIFY_PEER | (a.fail_unless_verified ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0), 0);
	if (!a.fail_unless_verified)
	    SSL_CTX_set_cert_verify_callback(ret, verify_dont_fail_cb, NULL);
	    
	if (a.verify_depth > 0)
	    SSL_CTX_set_verify_depth(ret, a.verify_depth);
	
	if (a.ca_file != NULL) {
	    r = SSL_CTX_load_verify_locations(ret, a.ca_file, NULL /* no CA-directory */); /* does not report failure if file does not exist ... */
	    if (!r) {
		err_pref_1 = " while processing certificate file ";
		err_pref_2 = a.ca_file;
		goto err;
	    }
	    
	    if (!a.client_p) {
		/* SSL_load_client_CA_file is a misnomer, it just creates a list of CNs. */
		SSL_CTX_set_client_CA_list(ret, SSL_load_client_CA_file(a.ca_file));
		/* SSL_CTX_set_client_CA_list does not have a return value;
		 * it does not really need one, but make sure
		 * (we really test if SSL_load_client_CA_file worked) */
		if (SSL_CTX_get_client_CA_list(ret) == NULL) {
		    tls_errprintf(1, apparg, "Could not set client CA list from \"%s\".\n", a.ca_file);
		    goto err_peek;
		}
	    }
	}
    }
    
    if (!a.client_p) {
	if (tls_dhe1024 == NULL) {
	    int i;

	    RAND_bytes((unsigned char *) &i, sizeof i);
	    /* make sure that i is non-negative -- pick one of the provided
	     * seeds */
	    if (i < 0)
		i = -i;
	    if (i < 0)
		i = 0;
	    tls_set_dhe1024(i, apparg);
	    if (tls_dhe1024 == NULL)
		goto err_return;
	}
	
	if (!SSL_CTX_set_tmp_dh(ret, tls_dhe1024))
	    goto err;

	/* avoid small subgroup attacks: */
	SSL_CTX_set_options(ret, SSL_OP_SINGLE_DH_USE);
    }
	
#ifndef NO_RSA
    if (!a.client_p && a.export_p) {
	RSA *tmpkey;

	tmpkey = RSA_generate_key(512, RSA_F4, 0, NULL);
	if (tmpkey == NULL)
	    goto err;
	if (!SSL_CTX_set_tmp_rsa(ret, tmpkey)) {
	    RSA_free(tmpkey);
	    goto err;
	}
	RSA_free(tmpkey); /* SSL_CTX_set_tmp_rsa uses a duplicate. */
    }
#endif
	
    return ret;
    
 err_peek:
    if (!ERR_peek_error())
	goto err_return;
 err:
    tls_openssl_errors(err_pref_1, err_pref_2, NULL, apparg);
 err_return:
    if (ret != NULL)
	SSL_CTX_free(ret);
    return NULL;
}


/*****************************************************************************/

static int
tls_socket_nonblocking(int fd)
{
    int v, r;

    v = fcntl(fd, F_GETFL, 0);
    if (v == -1) {
	if (errno == EINVAL)
	    return 0; /* already shut down -- ignore */
	return -1;
    }
    r = fcntl(fd, F_SETFL, v | O_NONBLOCK);
    if (r == -1) {
	if (errno == EINVAL)
	    return 0; /* already shut down -- ignore */
	return -1;
    }
    return 0;
}

static int
max(int a, int b)
{
    return a > b ? a : b;
}

static void
tls_sockets_select(int read_select_1, int read_select_2, int write_select_1, int write_select_2, int seconds /* timeout, -1 means no timeout */)
{
    int maxfd, n;
    fd_set reads, writes;
    struct timeval timeout;
    struct timeval *timeout_p;
    
    assert(read_select_1 >= -1 && read_select_2 >= -1 && write_select_1 >= -1 && write_select_2 >= -1);
    assert(read_select_1 < FD_SETSIZE && read_select_2 < FD_SETSIZE -1 && write_select_1 < FD_SETSIZE -1 && write_select_2 < FD_SETSIZE -1);

    maxfd = max(max(read_select_1, read_select_2), max(write_select_1, write_select_2));
    assert(maxfd >= 0);

    FD_ZERO(&reads);
    FD_ZERO(&writes);
    
    for(n = 0; n < 4; ++n) {
	int i = n % 2;
	int w = n >= 2;
	/* loop over all (i, w) in {0,1}x{0,1} */
	int fd;
	
	if (i == 0 && w == 0)
	    fd = read_select_1;
	else if (i == 1 && w == 0)
	    fd = read_select_2;
	else if (i == 0 && w == 1)
	    fd = write_select_1;
	else {
	    assert(i == 1 && w == 1);
	    fd = write_select_2;
	}
	
	if (fd >= 0) {
	    if (w == 0)
		FD_SET(fd, &reads);
	    else /* w == 1 */
		FD_SET(fd, &writes);
	}
    }

    if (seconds >= 0) {
	timeout.tv_sec = seconds;
	timeout.tv_usec = 0;
	timeout_p = &timeout;
    } else 
	timeout_p = NULL;

    DEBUG_MSG2("select no.", ++tls_select_count);
    select(maxfd + 1, &reads, &writes, (fd_set *) NULL, timeout_p);
    DEBUG_MSG("cont.");
}

/*****************************************************************************/

#define TUNNELBUFSIZE (16*1024)
struct tunnelbuf {
    char buf[TUNNELBUFSIZE];
    size_t len;
    size_t offset;
};

static int tls_connect_attempt(SSL *, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref);

static int tls_accept_attempt(SSL *, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref);

static int tls_write_attempt(SSL *, struct tunnelbuf *, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref);

static int tls_read_attempt(SSL *, struct tunnelbuf *, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref);

static int write_attempt(int fd, struct tunnelbuf *, int *select, int *closed, int *progress);

static int read_attempt(int fd, struct tunnelbuf *, int *select, int *closed, int *progress);

static void write_info(SSL *ssl, int *info_fd)
{
    if (*info_fd != -1) {
	long v;
	int v_ok;
	struct tls_x509_name_string peer;
	char infobuf[TLS_INFO_SIZE];
	int r;

	DEBUG_MSG("write_info");
	v = SSL_get_verify_result(ssl);
	v_ok = (v == X509_V_OK) ? 'A' : 'E'; /* Auth./Error */
	{
	    X509 *peercert;

	    peercert = SSL_get_peer_certificate(ssl);
	    tls_get_x509_subject_name_oneline(peercert, &peer);
	    if (peercert != NULL)
		X509_free(peercert);
	}
	if (peer.str[0] == '\0')
	    v_ok = '0'; /* no cert at all */
	else
	    if (strchr(peer.str, '\n')) {
		/* should not happen, but make sure */
		*strchr(peer.str, '\n') = '\0';
	    }
	r = snprintf(infobuf, sizeof infobuf, "%c:%s\n%s\n", v_ok, X509_verify_cert_error_string(v), peer.str);
	DEBUG_MSG2("snprintf", r);
	if (r == -1 || r >= sizeof infobuf)
	    r = sizeof infobuf - 1;
	write(*info_fd, infobuf, r);
	close (*info_fd);
	*info_fd = -1;
    }
}


/* tls_proxy expects that all fds are closed after return */
static void
tls_proxy(int clear_fd, int tls_fd, int info_fd, SSL_CTX *ctx, int client_p)
{
    struct tunnelbuf clear_to_tls, tls_to_clear;
    SSL *ssl;
    BIO *rbio, *wbio;
    int closed, in_handshake;
    const char *err_pref_1 = "", *err_pref_2 = "";
    const char *err_def = NULL;

    assert(clear_fd != -1);
    assert(tls_fd != -1);
    assert(clear_fd < FD_SETSIZE);
    assert(tls_fd < FD_SETSIZE);
    /* info_fd may be -1 */
    assert(ctx != NULL);

    tls_rand_seed_uniquely();

    tls_socket_nonblocking(clear_fd);
    DEBUG_MSG2("clear_fd", clear_fd);
    tls_socket_nonblocking(tls_fd);
    DEBUG_MSG2("tls_fd", tls_fd);

    ssl = SSL_new(ctx);
    if (ssl == NULL)
	goto err;
    DEBUG_MSG("SSL_new");
    if (!SSL_set_fd(ssl, tls_fd))
	goto err;
    rbio = SSL_get_rbio(ssl);
    wbio = SSL_get_wbio(ssl); /* should be the same, but who cares */
    assert(rbio != NULL);
    assert(wbio != NULL);
    if (client_p)
	SSL_set_connect_state(ssl);
    else
	SSL_set_accept_state(ssl);
    
    closed = 0;
    in_handshake = 1;
    tls_to_clear.len = 0;
    tls_to_clear.offset = 0;
    clear_to_tls.len = 0;
    clear_to_tls.offset = 0;

    err_def = "I/O error";
    
    /* loop finishes as soon as we detect that one side closed;
     * when all (program and OS) buffers have enough space,
     * the data from the last succesful read in each direction is transferred
     * before close */
    do {
	int clear_read_select = 0, clear_write_select = 0,
	    tls_read_select = 0, tls_write_select = 0,
	    progress = 0;
	int r;
	unsigned long num_read = BIO_number_read(rbio),
	    num_written = BIO_number_written(wbio);

	DEBUG_MSG2("loop iteration", ++tls_loop_count);

	if (in_handshake) {
	    DEBUG_MSG("in_handshake");
	    if (client_p)
		r = tls_connect_attempt(ssl, &tls_write_select, &tls_read_select, &closed, &progress, &err_pref_1);
	    else
		r = tls_accept_attempt(ssl, &tls_write_select, &tls_read_select, &closed, &progress, &err_pref_1);
	    if (r != 0) {
		write_info(ssl, &info_fd);
		goto err;
	    }
	    if (closed)
		goto err_return;
	    if (!SSL_in_init(ssl)) {
		in_handshake = 0;
		write_info(ssl, &info_fd);
	    }
	}
	
	if (clear_to_tls.len != 0 && !in_handshake) {
	    assert(!closed);
	    
	    r = tls_write_attempt(ssl, &clear_to_tls, &tls_write_select, &tls_read_select, &closed, &progress, &err_pref_1);
	    if (r != 0)
		goto err;
	    if (closed) {
		assert(progress);
		tls_to_clear.offset = 0;
		tls_to_clear.len = 0;
	    }
	}
	
	if (tls_to_clear.len != 0) {
	    assert(!closed);

	    r = write_attempt(clear_fd, &tls_to_clear, &clear_write_select, &closed, &progress);
	    if (r != 0)
		goto err_return;
	    if (closed) {
		assert(progress);
		clear_to_tls.offset = 0;
		clear_to_tls.len = 0;
	    }
	}
	
	if (!closed) {
	    if (clear_to_tls.offset + clear_to_tls.len < sizeof clear_to_tls.buf) {
		r = read_attempt(clear_fd, &clear_to_tls, &clear_read_select, &closed, &progress);
		if (r != 0)
		    goto err_return;
		if (closed) {
		    r = SSL_shutdown(ssl);
		    DEBUG_MSG2("SSL_shutdown", r);
		}
	    }
	}
	
	if (!closed && !in_handshake) {
	    if (tls_to_clear.offset + tls_to_clear.len < sizeof tls_to_clear.buf) {
		r = tls_read_attempt(ssl, &tls_to_clear, &tls_write_select, &tls_read_select, &closed, &progress, &err_pref_1);
		if (r != 0)
		    goto err;
		if (closed) {
		    r = SSL_shutdown(ssl);
		    DEBUG_MSG2("SSL_shutdown", r);
		}
	    }
	}

	if (!progress) {
	    DEBUG_MSG("!progress?");
	    if (num_read != BIO_number_read(rbio) || num_written != BIO_number_written(wbio))
		progress = 1;

	    if (!progress) {
		DEBUG_MSG("!progress");
		assert(clear_read_select || tls_read_select || clear_write_select || tls_write_select);
		tls_sockets_select(clear_read_select ? clear_fd : -1, tls_read_select ? tls_fd : -1, clear_write_select ? clear_fd : -1, tls_write_select ? tls_fd : -1, -1);
	    }
	}
    } while (!closed);
    return;

 err:
    tls_openssl_errors(err_pref_1, err_pref_2, err_def, tls_child_apparg);
 err_return:
    return;
}


static int
tls_get_error(SSL *ssl, int r, int *write_select, int *read_select, int *closed, int *progress)
{
    int err = SSL_get_error(ssl, r);

    if (err == SSL_ERROR_NONE) {
	assert(r > 0);
	*progress = 1;
	return 0;
    }

    assert(r <= 0);

    switch (err) {
    case SSL_ERROR_ZERO_RETURN:
	assert(r == 0);
	*closed = 1;
	*progress = 1;
	return 0;

    case SSL_ERROR_WANT_WRITE:
	*write_select = 1;
	return 0;
	
    case SSL_ERROR_WANT_READ:
	*read_select = 1;
	return 0;
    }

    return -1;
}

static int
tls_connect_attempt(SSL *ssl, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref)
{
    int n, r;

    DEBUG_MSG("tls_connect_attempt");
    n = SSL_connect(ssl);
    DEBUG_MSG2("SSL_connect",n);
    r = tls_get_error(ssl, n, write_select, read_select, closed, progress);
    if (r == -1)
	*err_pref = " during SSL_connect";
    return r;
}

static int
tls_accept_attempt(SSL *ssl, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref)
{
    int n, r;

    DEBUG_MSG("tls_accept_attempt");
    n = SSL_accept(ssl);
    DEBUG_MSG2("SSL_accept",n);
    r = tls_get_error(ssl, n, write_select, read_select, closed, progress);
    if (r == -1)
	*err_pref = " during SSL_accept";
    return r;
}

static int
tls_write_attempt(SSL *ssl, struct tunnelbuf *buf, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref)
{
    int n, r;

    DEBUG_MSG("tls_write_attempt");
    n = SSL_write(ssl, buf->buf + buf->offset, buf->len);
    DEBUG_MSG2("SSL_write",n);
    r = tls_get_error(ssl, n, write_select, read_select, closed, progress);
    if (n > 0) {
	buf->len -= n;
	assert(buf->len >= 0);
	if (buf->len == 0)
	    buf->offset = 0;
	else
	    buf->offset += n;
    }
    if (r == -1)
	*err_pref = " during SSL_write";
    return r;
}

static int
tls_read_attempt(SSL *ssl, struct tunnelbuf *buf, int *write_select, int *read_select, int *closed, int *progress, const char **err_pref)
{
    int n, r;
    size_t total;

    DEBUG_MSG("tls_read_attempt");
    total = buf->offset + buf->len;
    assert(total < sizeof buf->buf);
    n = SSL_read(ssl, buf->buf + total, (sizeof buf->buf) - total);
    DEBUG_MSG2("SSL_read",n);
    r = tls_get_error(ssl, n, write_select, read_select, closed, progress);
    if (n > 0) {
	buf->len += n;
	assert(buf->offset + buf->len <= sizeof buf->buf);
    }
    if (r == -1)
	*err_pref = " during SSL_read";
    return r;
}

static int
get_error(int r, int *select, int *closed, int *progress)
{
    if (r >= 0) {
	*progress = 1;
	if (r == 0)
	    *closed = 1;
	return 0;
    } else {
	assert(r == -1);
	if (errno == EAGAIN || errno == EWOULDBLOCK) {
	    *select = 1;
	    return 0;
	} else if (errno == EPIPE) {
	    *progress = 1;
	    *closed = 1;
	    return 0;
	} else
	    return -1;
    }
}

static int write_attempt(int fd, struct tunnelbuf *buf, int *select, int *closed, int *progress)
{
    int n, r;

    DEBUG_MSG("write_attempt");
    n = write(fd, buf->buf + buf->offset, buf->len);
    DEBUG_MSG2("write",n);
    r = get_error(n, select, closed, progress);
    if (n > 0) {
	buf->len -= n;
	assert(buf->len >= 0);
	if (buf->len == 0)
	    buf->offset = 0;
	else
	    buf->offset += n;
    }
    if (r == -1)
	tls_errprintf(1, tls_child_apparg, "write error: %s\n", strerror(errno));
    return r;
}
    
static int
read_attempt(int fd, struct tunnelbuf *buf, int *select, int *closed, int *progress)
{
    int n, r;
    size_t total;

    DEBUG_MSG("read_attempt");
    total = buf->offset + buf->len;
    assert(total < sizeof buf->buf);
    n = read(fd, buf->buf + total, (sizeof buf->buf) - total);
    DEBUG_MSG2("read",n);
    r = get_error(n, select, closed, progress);
    if (n > 0) {
	buf->len += n;
	assert(buf->offset + buf->len <= sizeof buf->buf);
    }
    if (r == -1)
	tls_errprintf(1, tls_child_apparg, "read error: %s\n", strerror(errno));
    return r;
}