Add OpenConnect provider plugin for AnyConnect VPN support
[framework/connectivity/connman.git] / plugins / openconnect.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2009  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, 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 (domain == NULL && !strcmp(key, "CISCO_DEF_DOMAIN"))
192                         domain = value;
193
194                 dbus_message_iter_next(&dict);
195         }
196
197         index = connman_provider_get_index(provider);
198         connman_provider_set_string(provider, "Domain", domain);
199         data->watch = connman_rtnl_add_newlink_watch(index,
200                                                      vpn_newlink, provider);
201
202         connman_inet_ifup(index);
203 }
204
205 static int oc_connect(struct connman_provider *provider)
206 {
207         struct oc_data *data = connman_provider_get_data(provider);
208         struct ifreq ifr;
209         int oc_fd, fd, i, index;
210         const char *vpnhost, *vpncookie, *cafile, *mtu;
211         int ret = 0;
212
213         if (data != NULL)
214                 return -EISCONN;
215
216         data = g_try_new0(struct oc_data, 1);
217         if (data == NULL)
218                 return -ENOMEM;
219
220         data->watch = 0;
221         data->flags = 0;
222         data->task = NULL;
223
224         connman_provider_set_data(provider, data);
225
226         vpnhost = connman_provider_get_string(provider, "OpenConnect.Host");
227         if (!vpnhost) {
228                 connman_error("OpenConnect.Host not set; cannot enable VPN");
229                 ret = -EINVAL;
230                 goto exist_err;
231         }
232
233         vpncookie = connman_provider_get_string(provider, "OpenConnect.Cookie");
234         if (!vpncookie) {
235                 connman_error("OpenConnect.Cookie not set; cannot enable VPN");
236                 ret = -EINVAL;
237                 goto exist_err;
238         }
239
240         cafile = connman_provider_get_string(provider, "OpenConnect.CACert");
241         mtu = connman_provider_get_string(provider, "VPN.MTU");
242
243         fd = open("/dev/net/tun", O_RDWR);
244         if (fd < 0) {
245                 i = -errno;
246                 connman_error("Failed to open /dev/net/tun: %s",
247                               strerror(errno));
248                 ret = i;
249                 goto exist_err;
250         }
251
252         memset(&ifr, 0, sizeof(ifr));
253         ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
254
255         for (i = 0; i < 256; i++) {
256                 sprintf(ifr.ifr_name, "vpn%d", i);
257
258                 if (!ioctl(fd, TUNSETIFF, (void *)&ifr))
259                         break;
260         }
261
262         if (i == 256) {
263                 connman_error("Failed to find available tun device");
264                 close(fd);
265                 ret = -ENODEV;
266                 goto exist_err;
267         }
268
269         data->if_name = (char *)g_strdup(ifr.ifr_name);
270         if (!data->if_name) {
271                 ret = -ENOMEM;
272                 goto exist_err;
273         }
274
275         if (ioctl(fd, TUNSETPERSIST, 1)) {
276                 i = -errno;
277                 connman_error("Failed to set tun persistent: %s",
278                               strerror(errno));
279                 close(fd);
280                 ret = i;
281                 goto exist_err;
282         }
283
284         close(fd);
285
286         index = connman_inet_ifindex(data->if_name);
287         if (index < 0) {
288                 connman_error("Failed to get tun ifindex");
289                 kill_tun(data->if_name);
290                 ret = -EIO;
291                 goto exist_err;
292         }
293         connman_provider_set_index(provider, index);
294
295         data->task = connman_task_create(OPENCONNECT);
296
297         if (data->task == NULL) {
298                 ret = -ENOMEM;
299                 kill_tun(data->if_name);
300                 goto exist_err;
301         }
302
303         if (connman_task_set_notify(data->task, "notify",
304                                         openconnect_task_notify, provider)) {
305                 ret = -ENOMEM;
306                 kill_tun(data->if_name);
307                 connman_task_destroy(data->task);
308                 data->task = NULL;
309                 goto exist_err;
310         }
311
312         if (cafile)
313                 connman_task_add_argument(data->task, "--cafile",
314                                                         (char *)cafile);
315         if (mtu)
316                 connman_task_add_argument(data->task, "--mtu", (char *)mtu);
317
318         connman_task_add_argument(data->task, "--syslog", NULL);
319         connman_task_add_argument(data->task, "--cookie-on-stdin", NULL);
320
321         connman_task_add_argument(data->task, "--script",
322                                   SCRIPTDIR "/openconnect-script");
323
324         connman_task_add_argument(data->task, "--interface", data->if_name);
325
326         connman_task_add_argument(data->task, (char *)vpnhost, NULL);
327
328         ret = connman_task_run(data->task, openconnect_died, provider,
329                                &oc_fd, NULL, NULL);
330         if (ret) {
331                 connman_error("Openconnect failed to start");
332                 kill_tun(data->if_name);
333                 ret = -EIO;
334                 connman_task_destroy(data->task);
335                 data->task = NULL;
336                 goto exist_err;
337         }
338
339         DBG("openconnect started with dev %s", data->if_name);
340
341         if (write(oc_fd, vpncookie, strlen(vpncookie)) !=
342             (ssize_t)strlen(vpncookie) ||
343             write(oc_fd, "\n", 1) != 1) {
344                 connman_error("openconnect failed to take cookie on stdin");
345                 connman_provider_set_data(provider, NULL);
346                 connman_task_stop(data->task);
347                 ret = -EIO;
348                 goto exist_err;
349         }
350
351         connman_provider_ref(provider);
352         return -EINPROGRESS;
353
354  exist_err:
355         connman_provider_set_index(provider, -1);
356         connman_provider_set_data(provider, NULL);
357         g_free(data);
358
359         return ret;
360 }
361
362 static int oc_probe(struct connman_provider *provider)
363 {
364         return 0;
365 }
366
367 static int oc_disconnect(struct connman_provider *provider)
368 {
369         struct oc_data *data = connman_provider_get_data(provider);
370
371         DBG("disconnect provider %p:", provider);
372
373         if (data == NULL)
374                 return 0;
375
376         if (data->watch != 0)
377                 connman_rtnl_remove_watch(data->watch);
378
379         data->watch = 0;
380         connman_task_stop(data->task);
381
382         return 0;
383 }
384
385 static int oc_remove(struct connman_provider *provider)
386 {
387         struct oc_data *data;
388
389         data = connman_provider_get_data(provider);
390         connman_provider_set_data(provider, NULL);
391         if (data == NULL)
392                 return 0;
393
394         if (data->watch != 0)
395                 connman_rtnl_remove_watch(data->watch);
396         data->watch = 0;
397         connman_task_stop(data->task);
398
399         g_usleep(G_USEC_PER_SEC);
400         kill_tun(data->if_name);
401         return 0;
402 }
403
404 static struct connman_provider_driver provider_driver = {
405         .name           = "openconnect",
406         .disconnect     = oc_disconnect,
407         .connect        = oc_connect,
408         .probe          = oc_probe,
409         .remove         = oc_remove,
410 };
411
412 static int openconnect_init(void)
413 {
414         connman_provider_driver_register(&provider_driver);
415
416         return 0;
417 }
418
419 static void openconnect_exit(void)
420 {
421         connman_provider_driver_unregister(&provider_driver);
422 }
423
424 CONNMAN_PLUGIN_DEFINE(openconnect, "OpenConnect VPN plugin", VERSION,
425         CONNMAN_PLUGIN_PRIORITY_DEFAULT, openconnect_init, openconnect_exit)