/
ssl.c
373 lines (326 loc) · 9.64 KB
1
/*
2
* OpenConnect (SSL + DTLS) VPN client
3
*
4
* Copyright © 2008-2012 Intel Corporation.
5
*
6
7
8
* Author: David Woodhouse <dwmw2@infradead.org>
*
* This program is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU Lesser General Public License
10
* version 2.1, as published by the Free Software Foundation.
11
*
12
* This program is distributed in the hope that it will be useful, but
13
14
15
16
17
18
19
20
21
22
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to:
*
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
23
*/
24
25
#include <sys/types.h>
26
#include <sys/socket.h>
27
28
#include <netinet/in.h>
#include <arpa/inet.h>
29
30
#include <netdb.h>
#include <unistd.h>
31
#include <fcntl.h>
32
#include <string.h>
33
#include <stdio.h>
34
#include <errno.h>
35
#include <stdlib.h>
36
#include <stdarg.h>
37
38
#if defined(__linux__)
#include <sys/vfs.h>
39
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__APPLE__)
40
41
#include <sys/param.h>
#include <sys/mount.h>
42
#elif defined (__sun__) || defined(__NetBSD__) || defined(__DragonFly__)
43
#include <sys/statvfs.h>
44
45
#elif defined (__GNU__)
#include <sys/statfs.h>
46
#endif
47
48
#include "openconnect-internal.h"
49
50
51
52
53
54
/* OSX < 1.6 doesn't have AI_NUMERICSERV */
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
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
static int cancellable_connect(struct openconnect_info *vpninfo, int sockfd,
const struct sockaddr *addr, socklen_t addrlen)
{
struct sockaddr_storage peer;
socklen_t peerlen = sizeof(peer);
fd_set wr_set, rd_set;
int maxfd = sockfd;
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL) | O_NONBLOCK);
if (connect(sockfd, addr, addrlen) < 0 && errno != EINPROGRESS)
return -1;
FD_ZERO(&wr_set);
FD_ZERO(&rd_set);
FD_SET(sockfd, &wr_set);
if (vpninfo->cancel_fd != -1) {
FD_SET(vpninfo->cancel_fd, &rd_set);
if (vpninfo->cancel_fd > sockfd)
maxfd = vpninfo->cancel_fd;
}
/* Later we'll render this whole exercise non-pointless by
including a 'cancelfd' here too. */
select(maxfd + 1, &rd_set, &wr_set, NULL, NULL);
if (vpninfo->cancel_fd != -1 && FD_ISSET(vpninfo->cancel_fd, &rd_set)) {
81
vpn_progress(vpninfo, PRG_ERR, _("Socket connect cancelled\n"));
82
83
84
85
86
87
88
89
90
errno = EINTR;
return -1;
}
/* Check whether connect() succeeded or failed by using
getpeername(). See http://cr.yp.to/docs/connect.html */
return getpeername(sockfd, (void *)&peer, &peerlen);
}
91
int connect_https_socket(struct openconnect_info *vpninfo)
92
{
93
int ssl_sock = -1;
94
95
int err;
96
97
98
if (!vpninfo->port)
vpninfo->port = 443;
99
if (vpninfo->peer_addr) {
100
101
102
103
104
105
106
107
108
109
110
#ifdef SOCK_CLOEXEC
ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_IP);
if (ssl_sock < 0)
#endif
{
ssl_sock = socket(vpninfo->peer_addr->sa_family, SOCK_STREAM, IPPROTO_IP);
if (ssl_sock < 0)
goto reconn_err;
fcntl(ssl_sock, F_SETFD, fcntl(ssl_sock, F_GETFD) | FD_CLOEXEC);
}
if (cancellable_connect(vpninfo, ssl_sock, vpninfo->peer_addr, vpninfo->peer_addrlen)) {
111
reconn_err:
112
113
114
115
116
117
118
119
120
if (vpninfo->proxy) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to reconnect to proxy %s\n"),
vpninfo->proxy);
} else {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to reconnect to host %s\n"),
vpninfo->hostname);
}
121
return -EINVAL;
122
}
123
124
125
} else {
struct addrinfo hints, *result, *rp;
126
char *hostname;
127
char port[6];
128
129
130
131
132
133
134
135
136
137
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
hints.ai_protocol = 0;
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
138
139
140
141
/* The 'port' variable is a string because it's easier
this way than if we pass NULL to getaddrinfo() and
then try to fill in the numeric value into
different types of returned sockaddr_in{6,}. */
142
#ifdef LIBPROXY_HDR
143
144
145
146
147
if (vpninfo->proxy_factory) {
char *url;
char **proxies;
int i = 0;
148
149
free(vpninfo->proxy_type);
vpninfo->proxy_type = NULL;
150
151
152
153
free(vpninfo->proxy);
vpninfo->proxy = NULL;
if (vpninfo->port == 443)
154
155
i = asprintf(&url, "https://%s/%s", vpninfo->hostname,
vpninfo->urlpath?:"");
156
else
157
158
159
160
i = asprintf(&url, "https://%s:%d/%s", vpninfo->hostname,
vpninfo->port, vpninfo->urlpath?:"");
if (i == -1)
return -ENOMEM;
161
162
163
164
proxies = px_proxy_factory_get_proxies(vpninfo->proxy_factory,
url);
165
i = 0;
166
while (proxies && proxies[i]) {
167
168
169
170
if (!vpninfo->proxy &&
(!strncmp(proxies[i], "http://", 7) ||
!strncmp(proxies[i], "socks://", 8) ||
!strncmp(proxies[i], "socks5://", 9)))
171
internal_parse_url(proxies[i], &vpninfo->proxy_type,
172
173
&vpninfo->proxy, &vpninfo->proxy_port,
NULL, 0);
174
175
176
177
178
i++;
}
free(url);
free(proxies);
if (vpninfo->proxy)
179
180
181
vpn_progress(vpninfo, PRG_TRACE,
_("Proxy from libproxy: %s://%s:%d/\n"),
vpninfo->proxy_type, vpninfo->proxy, vpninfo->port);
182
183
}
#endif
184
185
if (vpninfo->proxy) {
hostname = vpninfo->proxy;
186
snprintf(port, 6, "%d", vpninfo->proxy_port);
187
188
} else {
hostname = vpninfo->hostname;
189
snprintf(port, 6, "%d", vpninfo->port);
190
191
}
192
if (hostname[0] == '[' && hostname[strlen(hostname)-1] == ']') {
193
194
195
196
/* Solaris has no strndup(). */
int len = strlen(hostname) - 2;
char *new_hostname = malloc(len + 1);
if (!new_hostname)
197
return -ENOMEM;
198
199
200
201
memcpy(new_hostname, hostname + 1, len);
new_hostname[len] = 0;
hostname = new_hostname;
202
203
204
hints.ai_flags |= AI_NUMERICHOST;
}
205
err = getaddrinfo(hostname, port, &hints, &result);
206
207
208
if (hints.ai_flags & AI_NUMERICHOST)
free(hostname);
209
if (err) {
210
211
212
vpn_progress(vpninfo, PRG_ERR,
_("getaddrinfo failed for host '%s': %s\n"),
hostname, gai_strerror(err));
213
214
215
216
return -EINVAL;
}
for (rp = result; rp ; rp = rp->ai_next) {
217
218
219
220
char host[80];
if (!getnameinfo(rp->ai_addr, rp->ai_addrlen, host,
sizeof(host), NULL, 0, NI_NUMERICHOST))
221
vpn_progress(vpninfo, PRG_INFO,
222
223
224
225
226
_("Attempting to connect to %s%s%s:%s\n"),
rp->ai_family == AF_INET6?"[":"",
host,
rp->ai_family == AF_INET6?"]":"",
port);
227
228
229
230
231
ssl_sock = socket(rp->ai_family, rp->ai_socktype,
rp->ai_protocol);
if (ssl_sock < 0)
continue;
232
if (cancellable_connect(vpninfo, ssl_sock, rp->ai_addr, rp->ai_addrlen) >= 0) {
233
234
235
236
/* Store the peer address we actually used, so that DTLS can
use it again later */
vpninfo->peer_addr = malloc(rp->ai_addrlen);
if (!vpninfo->peer_addr) {
237
238
vpn_progress(vpninfo, PRG_ERR,
_("Failed to allocate sockaddr storage\n"));
239
240
241
242
243
244
245
246
247
248
249
250
251
close(ssl_sock);
return -ENOMEM;
}
vpninfo->peer_addrlen = rp->ai_addrlen;
memcpy(vpninfo->peer_addr, rp->ai_addr, rp->ai_addrlen);
break;
}
close(ssl_sock);
ssl_sock = -1;
}
freeaddrinfo(result);
if (ssl_sock < 0) {
252
253
254
vpn_progress(vpninfo, PRG_ERR,
_("Failed to connect to host %s\n"),
vpninfo->proxy?:vpninfo->hostname);
255
256
return -EINVAL;
}
257
258
}
259
if (vpninfo->proxy) {
260
err = process_proxy(vpninfo, ssl_sock);
261
262
263
264
265
266
if (err) {
close(ssl_sock);
return err;
}
}
267
268
269
return ssl_sock;
}
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
int __attribute__ ((format (printf, 2, 3)))
openconnect_SSL_printf(struct openconnect_info *vpninfo, const char *fmt, ...)
{
char buf[1024];
va_list args;
buf[1023] = 0;
va_start(args, fmt);
vsnprintf(buf, 1023, fmt, args);
va_end(args);
return openconnect_SSL_write(vpninfo, buf, strlen(buf));
}
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
int request_passphrase(struct openconnect_info *vpninfo,
char **response, const char *fmt, ...)
{
struct oc_auth_form f;
struct oc_form_opt o;
char buf[1024];
va_list args;
int ret;
if (!vpninfo->process_auth_form)
return -EINVAL;
buf[1023] = 0;
memset(&f, 0, sizeof(f));
va_start(args, fmt);
vsnprintf(buf, 1023, fmt, args);
va_end(args);
f.auth_id = (char *)"ssl_certificate";
f.opts = &o;
o.next = NULL;
o.type = OC_FORM_OPT_PASSWORD;
o.name = (char *)"passphrase";
o.label = buf;
o.value = NULL;
312
ret = vpninfo->process_auth_form(vpninfo->cbdata, &f);
313
314
315
316
317
318
319
320
if (!ret) {
*response = o.value;
return 0;
}
return -EIO;
}
321
#if defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__)
322
int openconnect_passphrase_from_fsid(struct openconnect_info *vpninfo)
323
324
325
326
327
{
struct statvfs buf;
if (statvfs(vpninfo->sslkey, &buf)) {
int err = errno;
328
329
vpn_progress(vpninfo, PRG_ERR, _("statvfs: %s\n"),
strerror(errno));
330
331
return -err;
}
332
333
if (asprintf(&vpninfo->cert_password, "%lx", buf.f_fsid))
return -ENOMEM;
334
335
336
return 0;
}
#else
337
int openconnect_passphrase_from_fsid(struct openconnect_info *vpninfo)
338
339
340
341
342
343
344
{
struct statfs buf;
unsigned *fsid = (unsigned *)&buf.f_fsid;
unsigned long long fsid64;
if (statfs(vpninfo->sslkey, &buf)) {
int err = errno;
345
346
vpn_progress(vpninfo, PRG_ERR, _("statfs: %s\n"),
strerror(errno));
347
348
349
return -err;
}
fsid64 = ((unsigned long long)fsid[0] << 32) | fsid[1];
350
351
352
if (asprintf(&vpninfo->cert_password, "%llx", fsid64))
return -ENOMEM;
353
354
return 0;
}
355
#endif
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
#if defined(OPENCONNECT_OPENSSL) || defined (DTLS_OPENSSL)
/* We put this here rather than in openssl.c because it might be needed
for OpenSSL DTLS support even when GnuTLS is being used for HTTPS */
#include <openssl/err.h>
static int print_err(const char *str, size_t len, void *ptr)
{
struct openconnect_info *vpninfo = ptr;
vpn_progress(vpninfo, PRG_ERR, "%s", str);
return 0;
}
void openconnect_report_ssl_errors(struct openconnect_info *vpninfo)
{
ERR_print_errors_cb(print_err, vpninfo);
}
#endif