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