811a7fac9e3d7fe2cda89598a07221237fedf8a2
[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 #if defined(__sun__) || defined(__NetBSD__) || defined(__DragonFly__)
286 int openconnect_passphrase_from_fsid(struct openconnect_info *vpninfo)
287 {
288         struct statvfs buf;
289
290         if (statvfs(vpninfo->sslkey, &buf)) {
291                 int err = errno;
292                 vpn_progress(vpninfo, PRG_ERR, _("statvfs: %s\n"),
293                              strerror(errno));
294                 return -err;
295         }
296         if (asprintf(&vpninfo->cert_password, "%lx", buf.f_fsid))
297                 return -ENOMEM;
298         return 0;
299 }
300 #else
301 int openconnect_passphrase_from_fsid(struct openconnect_info *vpninfo)
302 {
303         struct statfs buf;
304         unsigned *fsid = (unsigned *)&buf.f_fsid;
305         unsigned long long fsid64;
306
307         if (statfs(vpninfo->sslkey, &buf)) {
308                 int err = errno;
309                 vpn_progress(vpninfo, PRG_ERR, _("statfs: %s\n"),
310                              strerror(errno));
311                 return -err;
312         }
313         fsid64 = ((unsigned long long)fsid[0] << 32) | fsid[1];
314
315         if (asprintf(&vpninfo->cert_password, "%llx", fsid64))
316                 return -ENOMEM;
317         return 0;
318 }
319 #endif