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