Fix provider refcounting
[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 enum oc_state {
56         OC_STATE_UNKNOWN       = 0,
57         OC_STATE_IDLE          = 1,
58         OC_STATE_CONNECT       = 2,
59         OC_STATE_READY         = 3,
60         OC_STATE_DISCONNECT    = 4,
61         OC_STATE_FAILURE       = 5,
62 };
63
64 struct oc_data {
65         struct connman_provider *provider;
66         char *if_name;
67         unsigned flags;
68         unsigned int watch;
69         unsigned int state;
70         struct connman_task *task;
71 };
72
73 static int kill_tun(char *tun_name)
74 {
75         struct ifreq ifr;
76         int fd, err;
77
78         memset(&ifr, 0, sizeof(ifr));
79         ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
80         sprintf(ifr.ifr_name, "%s", tun_name);
81
82         fd = open("/dev/net/tun", O_RDWR);
83         if (fd < 0) {
84                 err = -errno;
85                 connman_error("Failed to open /dev/net/tun to device %s: %s",
86                               tun_name, strerror(errno));
87                 return err;
88         }
89
90         if (ioctl(fd, TUNSETIFF, (void *)&ifr)) {
91                 err = -errno;
92                 connman_error("Failed to TUNSETIFF for device %s to it: %s",
93                               tun_name, strerror(errno));
94                 close(fd);
95                 return err;
96         }
97
98         if (ioctl(fd, TUNSETPERSIST, 0)) {
99                 err = -errno;
100                 connman_error("Failed to set tun device %s nonpersistent: %s",
101                               tun_name, strerror(errno));
102                 close(fd);
103                 return err;
104         }
105         close(fd);
106         DBG("Killed tun device %s", tun_name);
107         return 0;
108 }
109
110 static void openconnect_died(struct connman_task *task, void *user_data)
111 {
112         struct connman_provider *provider = user_data;
113         struct oc_data *data = connman_provider_get_data(provider);
114         int state = data->state;
115
116         DBG("provider %p data %p", provider, data);
117
118         if (!data)
119                 goto oc_exit;
120
121         kill_tun(data->if_name);
122         connman_provider_set_data(provider, NULL);
123         connman_rtnl_remove_watch(data->watch);
124         connman_provider_unref(data->provider);
125         g_free(data);
126
127  oc_exit:
128         if (state != OC_STATE_READY && state != OC_STATE_DISCONNECT)
129                 connman_provider_set_state(provider,
130                                                 CONNMAN_PROVIDER_STATE_FAILURE);
131         else
132                 connman_provider_set_state(provider,
133                                                 CONNMAN_PROVIDER_STATE_IDLE);
134
135         connman_provider_set_index(provider, -1);
136         connman_task_destroy(task);
137 }
138
139 static void vpn_newlink(unsigned flags, unsigned change, void *user_data)
140 {
141         struct connman_provider *provider = user_data;
142         struct oc_data *data = connman_provider_get_data(provider);
143
144         if ((data->flags & IFF_UP) != (flags & IFF_UP)) {
145                 if (flags & IFF_UP) {
146                         data->state = OC_STATE_READY;
147                         connman_provider_set_state(provider,
148                                         CONNMAN_PROVIDER_STATE_READY);
149                 }
150         }
151         data->flags = flags;
152 }
153
154 static void openconnect_task_notify(struct connman_task *task,
155                                     DBusMessage *msg, void *user_data)
156 {
157         DBusMessageIter iter, dict;
158         struct connman_provider *provider = user_data;
159         struct oc_data *data;
160         const char *reason, *key, *value;
161         const char *domain = NULL;
162         int index;
163
164         dbus_message_iter_init(msg, &iter);
165
166         dbus_message_iter_get_basic(&iter, &reason);
167         dbus_message_iter_next(&iter);
168
169         if (!provider) {
170                 connman_error("No provider found");
171                 return;
172         }
173
174         data = connman_provider_get_data(provider);
175         if (!data) {
176                 DBG("provider %p no data", provider);
177                 return;
178         }
179
180         if (strcmp(reason, "connect")) {
181                 connman_provider_set_state(provider,
182                                         CONNMAN_PROVIDER_STATE_DISCONNECT);
183                 return;
184         }
185
186         domain = connman_provider_get_string(provider, "VPN.Domain");
187
188         dbus_message_iter_recurse(&iter, &dict);
189
190         while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
191                 DBusMessageIter entry;
192
193                 dbus_message_iter_recurse(&dict, &entry);
194                 dbus_message_iter_get_basic(&entry, &key);
195                 dbus_message_iter_next(&entry);
196                 dbus_message_iter_get_basic(&entry, &value);
197
198                 if (strcmp(key, "CISCO_CSTP_OPTIONS"))
199                         DBG("%s = %s", key, value);
200
201                 if (!strcmp(key, "VPNGATEWAY"))
202                         connman_provider_set_string(provider, "Gateway", value);
203
204                 if (!strcmp(key, "INTERNAL_IP4_ADDRESS"))
205                         connman_provider_set_string(provider, "Address", value);
206
207                 if (!strcmp(key, "INTERNAL_IP4_NETMASK"))
208                         connman_provider_set_string(provider, "Netmask", value);
209
210                 if (!strcmp(key, "INTERNAL_IP4_DNS"))
211                         connman_provider_set_string(provider, "DNS", value);
212
213                 if (!strcmp(key, "CISCO_PROXY_PAC"))
214                         connman_provider_set_string(provider, "PAC", value);
215
216                 if (domain == NULL && !strcmp(key, "CISCO_DEF_DOMAIN"))
217                         domain = value;
218
219                 dbus_message_iter_next(&dict);
220         }
221
222         index = connman_provider_get_index(provider);
223         connman_provider_set_string(provider, "Domain", domain);
224         data->watch = connman_rtnl_add_newlink_watch(index,
225                                                      vpn_newlink, provider);
226
227         connman_inet_ifup(index);
228 }
229
230 static int oc_connect(struct connman_provider *provider)
231 {
232         struct oc_data *data = connman_provider_get_data(provider);
233         struct ifreq ifr;
234         int oc_fd, fd, i, index;
235         const char *vpnhost, *vpncookie, *cafile, *mtu;
236         int ret = 0;
237
238         if (data != NULL)
239                 return -EISCONN;
240
241         data = g_try_new0(struct oc_data, 1);
242         if (data == NULL)
243                 return -ENOMEM;
244
245         data->provider = connman_provider_ref(provider);
246         data->watch = 0;
247         data->flags = 0;
248         data->task = NULL;
249         data->state = OC_STATE_IDLE;
250
251         connman_provider_set_data(provider, data);
252
253         vpnhost = connman_provider_get_string(provider, "Host");
254         if (!vpnhost) {
255                 connman_error("Host not set; cannot enable VPN");
256                 ret = -EINVAL;
257                 goto exist_err;
258         }
259
260         vpncookie = connman_provider_get_string(provider, "OpenConnect.Cookie");
261         if (!vpncookie) {
262                 connman_error("OpenConnect.Cookie not set; cannot enable VPN");
263                 ret = -EINVAL;
264                 goto exist_err;
265         }
266
267         cafile = connman_provider_get_string(provider, "OpenConnect.CACert");
268         mtu = connman_provider_get_string(provider, "VPN.MTU");
269
270         fd = open("/dev/net/tun", O_RDWR);
271         if (fd < 0) {
272                 i = -errno;
273                 connman_error("Failed to open /dev/net/tun: %s",
274                               strerror(errno));
275                 ret = i;
276                 goto exist_err;
277         }
278
279         memset(&ifr, 0, sizeof(ifr));
280         ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
281
282         for (i = 0; i < 256; i++) {
283                 sprintf(ifr.ifr_name, "vpn%d", i);
284
285                 if (!ioctl(fd, TUNSETIFF, (void *)&ifr))
286                         break;
287         }
288
289         if (i == 256) {
290                 connman_error("Failed to find available tun device");
291                 close(fd);
292                 ret = -ENODEV;
293                 goto exist_err;
294         }
295
296         data->if_name = (char *)g_strdup(ifr.ifr_name);
297         if (!data->if_name) {
298                 ret = -ENOMEM;
299                 goto exist_err;
300         }
301
302         if (ioctl(fd, TUNSETPERSIST, 1)) {
303                 i = -errno;
304                 connman_error("Failed to set tun persistent: %s",
305                               strerror(errno));
306                 close(fd);
307                 ret = i;
308                 goto exist_err;
309         }
310
311         close(fd);
312
313         index = connman_inet_ifindex(data->if_name);
314         if (index < 0) {
315                 connman_error("Failed to get tun ifindex");
316                 kill_tun(data->if_name);
317                 ret = -EIO;
318                 goto exist_err;
319         }
320         connman_provider_set_index(provider, index);
321
322         data->task = connman_task_create(OPENCONNECT);
323
324         if (data->task == NULL) {
325                 ret = -ENOMEM;
326                 kill_tun(data->if_name);
327                 goto exist_err;
328         }
329
330         if (connman_task_set_notify(data->task, "notify",
331                                         openconnect_task_notify, provider)) {
332                 ret = -ENOMEM;
333                 kill_tun(data->if_name);
334                 connman_task_destroy(data->task);
335                 data->task = NULL;
336                 goto exist_err;
337         }
338
339         if (cafile)
340                 connman_task_add_argument(data->task, "--cafile",
341                                                         (char *)cafile);
342         if (mtu)
343                 connman_task_add_argument(data->task, "--mtu", (char *)mtu);
344
345         connman_task_add_argument(data->task, "--syslog", NULL);
346         connman_task_add_argument(data->task, "--cookie-on-stdin", NULL);
347
348         connman_task_add_argument(data->task, "--script",
349                                   SCRIPTDIR "/openconnect-script");
350
351         connman_task_add_argument(data->task, "--interface", data->if_name);
352
353         connman_task_add_argument(data->task, (char *)vpnhost, NULL);
354
355         ret = connman_task_run(data->task, openconnect_died, provider,
356                                &oc_fd, NULL, NULL);
357         if (ret) {
358                 connman_error("Openconnect failed to start");
359                 kill_tun(data->if_name);
360                 ret = -EIO;
361                 connman_task_destroy(data->task);
362                 data->task = NULL;
363                 goto exist_err;
364         }
365
366         DBG("openconnect started with dev %s", data->if_name);
367
368         if (write(oc_fd, vpncookie, strlen(vpncookie)) !=
369             (ssize_t)strlen(vpncookie) ||
370             write(oc_fd, "\n", 1) != 1) {
371                 connman_error("openconnect failed to take cookie on stdin");
372                 connman_provider_set_data(provider, NULL);
373                 connman_task_stop(data->task);
374                 ret = -EIO;
375                 goto exist_err;
376         }
377
378         data->state = OC_STATE_CONNECT;
379
380         return -EINPROGRESS;
381
382  exist_err:
383         connman_provider_set_index(provider, -1);
384         connman_provider_set_data(provider, NULL);
385         connman_provider_unref(data->provider);
386         g_free(data);
387
388         return ret;
389 }
390
391 static int oc_probe(struct connman_provider *provider)
392 {
393         return 0;
394 }
395
396 static int oc_disconnect(struct connman_provider *provider)
397 {
398         struct oc_data *data = connman_provider_get_data(provider);
399
400         DBG("disconnect provider %p:", provider);
401
402         if (data == NULL)
403                 return 0;
404
405         if (data->watch != 0)
406                 connman_rtnl_remove_watch(data->watch);
407
408         data->watch = 0;
409         data->state = OC_STATE_DISCONNECT;
410         connman_task_stop(data->task);
411
412         return 0;
413 }
414
415 static int oc_remove(struct connman_provider *provider)
416 {
417         struct oc_data *data;
418
419         data = connman_provider_get_data(provider);
420         connman_provider_set_data(provider, NULL);
421         if (data == NULL)
422                 return 0;
423
424         if (data->watch != 0)
425                 connman_rtnl_remove_watch(data->watch);
426         data->watch = 0;
427         connman_task_stop(data->task);
428
429         g_usleep(G_USEC_PER_SEC);
430         kill_tun(data->if_name);
431         return 0;
432 }
433
434 static struct connman_provider_driver provider_driver = {
435         .name           = "openconnect",
436         .disconnect     = oc_disconnect,
437         .connect        = oc_connect,
438         .probe          = oc_probe,
439         .remove         = oc_remove,
440 };
441
442 static int openconnect_init(void)
443 {
444         connman_provider_driver_register(&provider_driver);
445
446         return 0;
447 }
448
449 static void openconnect_exit(void)
450 {
451         connman_provider_driver_unregister(&provider_driver);
452 }
453
454 CONNMAN_PLUGIN_DEFINE(openconnect, "OpenConnect VPN plugin", VERSION,
455         CONNMAN_PLUGIN_PRIORITY_DEFAULT, openconnect_init, openconnect_exit)