891aa8764a91ff3adecb22e2340dd7a44aadd00c
[framework/connectivity/connman.git] / plugins / openconnect.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <string.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 #include <stdio.h>
32 #include <errno.h>
33 #include <sys/ioctl.h>
34 #include <linux/if_tun.h>
35 #include <net/if.h>
36 #include <stdint.h>
37
38 #include <glib/garray.h>
39 #include <glib/gerror.h>
40 #include <glib/gmain.h>
41 #include <glib/gspawn.h>
42
43 #define CONNMAN_API_SUBJECT_TO_CHANGE
44 #include <connman/plugin.h>
45 #include <connman/device.h>
46 #include <connman/element.h>
47 #include <connman/provider.h>
48 #include <connman/log.h>
49 #include <connman/element.h>
50 #include <connman/rtnl.h>
51 #include <connman/task.h>
52
53 #include "inet.h"
54
55 struct oc_data {
56         char *if_name;
57         unsigned flags;
58         unsigned int watch;
59         struct connman_task *task;
60 };
61
62 static int kill_tun(char *tun_name)
63 {
64         struct ifreq ifr;
65         int fd, err;
66
67         memset(&ifr, 0, sizeof(ifr));
68         ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
69         sprintf(ifr.ifr_name, "%s", tun_name);
70
71         fd = open("/dev/net/tun", O_RDWR);
72         if (fd < 0) {
73                 err = -errno;
74                 connman_error("Failed to open /dev/net/tun to device %s: %s",
75                               tun_name, strerror(errno));
76                 return err;
77         }
78
79         if (ioctl(fd, TUNSETIFF, (void *)&ifr)) {
80                 err = -errno;
81                 connman_error("Failed to TUNSETIFF for device %s to it: %s",
82                               tun_name, strerror(errno));
83                 close(fd);
84                 return err;
85         }
86
87         if (ioctl(fd, TUNSETPERSIST, 0)) {
88                 err = -errno;
89                 connman_error("Failed to set tun device %s nonpersistent: %s",
90                               tun_name, strerror(errno));
91                 close(fd);
92                 return err;
93         }
94         close(fd);
95         DBG("Killed tun device %s", tun_name);
96         return 0;
97 }
98
99 static void openconnect_died(struct connman_task *task, void *user_data)
100 {
101         struct connman_provider *provider = user_data;
102         struct oc_data *data = connman_provider_get_data(provider);
103
104         DBG("provider %p data %p", provider, data);
105
106         if (!data)
107                 goto oc_exit;
108
109         kill_tun(data->if_name);
110         connman_provider_set_data(provider, NULL);
111         connman_rtnl_remove_watch(data->watch);
112         g_free(data);
113
114  oc_exit:
115         connman_provider_set_connected(provider, FALSE);
116         connman_provider_set_index(provider, -1);
117         connman_provider_unref(provider);
118         connman_task_destroy(task);
119 }
120
121 static void vpn_newlink(unsigned flags, unsigned change, void *user_data)
122 {
123         struct connman_provider *provider = user_data;
124         struct oc_data *data = connman_provider_get_data(provider);
125
126         if ((data->flags & IFF_UP) != (flags & IFF_UP)) {
127                 if (flags & IFF_UP)
128                         connman_provider_set_connected(provider, TRUE);
129         }
130         data->flags = flags;
131 }
132
133 static void openconnect_task_notify(struct connman_task *task,
134                                     DBusMessage *msg, void *user_data)
135 {
136         DBusMessageIter iter, dict;
137         struct connman_provider *provider = user_data;
138         struct oc_data *data;
139         const char *reason, *key, *value;
140         const char *domain = NULL;
141         int index;
142
143         dbus_message_iter_init(msg, &iter);
144
145         dbus_message_iter_get_basic(&iter, &reason);
146         dbus_message_iter_next(&iter);
147
148         if (!provider) {
149                 connman_error("No provider found");
150                 return;
151         }
152
153         data = connman_provider_get_data(provider);
154         if (!data) {
155                 DBG("provider %p no data", provider);
156                 return;
157         }
158
159         if (strcmp(reason, "connect")) {
160                 connman_provider_set_connected(provider, FALSE);
161                 return;
162         }
163
164         domain = connman_provider_get_string(provider, "VPN.Domain");
165
166         dbus_message_iter_recurse(&iter, &dict);
167
168         while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
169                 DBusMessageIter entry;
170
171                 dbus_message_iter_recurse(&dict, &entry);
172                 dbus_message_iter_get_basic(&entry, &key);
173                 dbus_message_iter_next(&entry);
174                 dbus_message_iter_get_basic(&entry, &value);
175
176                 if (strcmp(key, "CISCO_CSTP_OPTIONS"))
177                         DBG("%s = %s", key, value);
178
179                 if (!strcmp(key, "VPNGATEWAY"))
180                         connman_provider_set_string(provider, "Gateway", value);
181
182                 if (!strcmp(key, "INTERNAL_IP4_ADDRESS"))
183                         connman_provider_set_string(provider, "Address", value);
184
185                 if (!strcmp(key, "INTERNAL_IP4_NETMASK"))
186                         connman_provider_set_string(provider, "Netmask", value);
187
188                 if (!strcmp(key, "INTERNAL_IP4_DNS"))
189                         connman_provider_set_string(provider, "DNS", value);
190
191                 if (!strcmp(key, "CISCO_PROXY_PAC"))
192                         connman_provider_set_string(provider, "PAC", value);
193
194                 if (domain == NULL && !strcmp(key, "CISCO_DEF_DOMAIN"))
195                         domain = value;
196
197                 dbus_message_iter_next(&dict);
198         }
199
200         index = connman_provider_get_index(provider);
201         connman_provider_set_string(provider, "Domain", domain);
202         data->watch = connman_rtnl_add_newlink_watch(index,
203                                                      vpn_newlink, provider);
204
205         connman_inet_ifup(index);
206 }
207
208 static int oc_connect(struct connman_provider *provider)
209 {
210         struct oc_data *data = connman_provider_get_data(provider);
211         struct ifreq ifr;
212         int oc_fd, fd, i, index;
213         const char *vpnhost, *vpncookie, *cafile, *mtu;
214         int ret = 0;
215
216         if (data != NULL)
217                 return -EISCONN;
218
219         data = g_try_new0(struct oc_data, 1);
220         if (data == NULL)
221                 return -ENOMEM;
222
223         data->watch = 0;
224         data->flags = 0;
225         data->task = NULL;
226
227         connman_provider_set_data(provider, data);
228
229         vpnhost = connman_provider_get_string(provider, "OpenConnect.Host");
230         if (!vpnhost) {
231                 connman_error("OpenConnect.Host not set; cannot enable VPN");
232                 ret = -EINVAL;
233                 goto exist_err;
234         }
235
236         vpncookie = connman_provider_get_string(provider, "OpenConnect.Cookie");
237         if (!vpncookie) {
238                 connman_error("OpenConnect.Cookie not set; cannot enable VPN");
239                 ret = -EINVAL;
240                 goto exist_err;
241         }
242
243         cafile = connman_provider_get_string(provider, "OpenConnect.CACert");
244         mtu = connman_provider_get_string(provider, "VPN.MTU");
245
246         fd = open("/dev/net/tun", O_RDWR);
247         if (fd < 0) {
248                 i = -errno;
249                 connman_error("Failed to open /dev/net/tun: %s",
250                               strerror(errno));
251                 ret = i;
252                 goto exist_err;
253         }
254
255         memset(&ifr, 0, sizeof(ifr));
256         ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
257
258         for (i = 0; i < 256; i++) {
259                 sprintf(ifr.ifr_name, "vpn%d", i);
260
261                 if (!ioctl(fd, TUNSETIFF, (void *)&ifr))
262                         break;
263         }
264
265         if (i == 256) {
266                 connman_error("Failed to find available tun device");
267                 close(fd);
268                 ret = -ENODEV;
269                 goto exist_err;
270         }
271
272         data->if_name = (char *)g_strdup(ifr.ifr_name);
273         if (!data->if_name) {
274                 ret = -ENOMEM;
275                 goto exist_err;
276         }
277
278         if (ioctl(fd, TUNSETPERSIST, 1)) {
279                 i = -errno;
280                 connman_error("Failed to set tun persistent: %s",
281                               strerror(errno));
282                 close(fd);
283                 ret = i;
284                 goto exist_err;
285         }
286
287         close(fd);
288
289         index = connman_inet_ifindex(data->if_name);
290         if (index < 0) {
291                 connman_error("Failed to get tun ifindex");
292                 kill_tun(data->if_name);
293                 ret = -EIO;
294                 goto exist_err;
295         }
296         connman_provider_set_index(provider, index);
297
298         data->task = connman_task_create(OPENCONNECT);
299
300         if (data->task == NULL) {
301                 ret = -ENOMEM;
302                 kill_tun(data->if_name);
303                 goto exist_err;
304         }
305
306         if (connman_task_set_notify(data->task, "notify",
307                                         openconnect_task_notify, provider)) {
308                 ret = -ENOMEM;
309                 kill_tun(data->if_name);
310                 connman_task_destroy(data->task);
311                 data->task = NULL;
312                 goto exist_err;
313         }
314
315         if (cafile)
316                 connman_task_add_argument(data->task, "--cafile",
317                                                         (char *)cafile);
318         if (mtu)
319                 connman_task_add_argument(data->task, "--mtu", (char *)mtu);
320
321         connman_task_add_argument(data->task, "--syslog", NULL);
322         connman_task_add_argument(data->task, "--cookie-on-stdin", NULL);
323
324         connman_task_add_argument(data->task, "--script",
325                                   SCRIPTDIR "/openconnect-script");
326
327         connman_task_add_argument(data->task, "--interface", data->if_name);
328
329         connman_task_add_argument(data->task, (char *)vpnhost, NULL);
330
331         ret = connman_task_run(data->task, openconnect_died, provider,
332                                &oc_fd, NULL, NULL);
333         if (ret) {
334                 connman_error("Openconnect failed to start");
335                 kill_tun(data->if_name);
336                 ret = -EIO;
337                 connman_task_destroy(data->task);
338                 data->task = NULL;
339                 goto exist_err;
340         }
341
342         DBG("openconnect started with dev %s", data->if_name);
343
344         if (write(oc_fd, vpncookie, strlen(vpncookie)) !=
345             (ssize_t)strlen(vpncookie) ||
346             write(oc_fd, "\n", 1) != 1) {
347                 connman_error("openconnect failed to take cookie on stdin");
348                 connman_provider_set_data(provider, NULL);
349                 connman_task_stop(data->task);
350                 ret = -EIO;
351                 goto exist_err;
352         }
353
354         connman_provider_ref(provider);
355         return -EINPROGRESS;
356
357  exist_err:
358         connman_provider_set_index(provider, -1);
359         connman_provider_set_data(provider, NULL);
360         g_free(data);
361
362         return ret;
363 }
364
365 static int oc_probe(struct connman_provider *provider)
366 {
367         return 0;
368 }
369
370 static int oc_disconnect(struct connman_provider *provider)
371 {
372         struct oc_data *data = connman_provider_get_data(provider);
373
374         DBG("disconnect provider %p:", provider);
375
376         if (data == NULL)
377                 return 0;
378
379         if (data->watch != 0)
380                 connman_rtnl_remove_watch(data->watch);
381
382         data->watch = 0;
383         connman_task_stop(data->task);
384
385         return 0;
386 }
387
388 static int oc_remove(struct connman_provider *provider)
389 {
390         struct oc_data *data;
391
392         data = connman_provider_get_data(provider);
393         connman_provider_set_data(provider, NULL);
394         if (data == NULL)
395                 return 0;
396
397         if (data->watch != 0)
398                 connman_rtnl_remove_watch(data->watch);
399         data->watch = 0;
400         connman_task_stop(data->task);
401
402         g_usleep(G_USEC_PER_SEC);
403         kill_tun(data->if_name);
404         return 0;
405 }
406
407 static struct connman_provider_driver provider_driver = {
408         .name           = "openconnect",
409         .disconnect     = oc_disconnect,
410         .connect        = oc_connect,
411         .probe          = oc_probe,
412         .remove         = oc_remove,
413 };
414
415 static int openconnect_init(void)
416 {
417         connman_provider_driver_register(&provider_driver);
418
419         return 0;
420 }
421
422 static void openconnect_exit(void)
423 {
424         connman_provider_driver_unregister(&provider_driver);
425 }
426
427 CONNMAN_PLUGIN_DEFINE(openconnect, "OpenConnect VPN plugin", VERSION,
428         CONNMAN_PLUGIN_PRIORITY_DEFAULT, openconnect_init, openconnect_exit)