Base Code merged to SPIN 2.4
[platform/upstream/connman.git] / vpn / plugins / openconnect.c
1 /*
2  *
3  *  ConnMan VPN daemon
4  *
5  *  Copyright (C) 2007-2013  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 <stdlib.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <net/if.h>
31
32 #include <glib.h>
33
34 #define CONNMAN_API_SUBJECT_TO_CHANGE
35 #include <connman/plugin.h>
36 #include <connman/log.h>
37 #include <connman/task.h>
38 #include <connman/ipconfig.h>
39 #include <connman/dbus.h>
40 #include <connman/agent.h>
41 #include <connman/setting.h>
42 #include <connman/vpn-dbus.h>
43
44 #include "../vpn-provider.h"
45 #include "../vpn-agent.h"
46
47 #include "vpn.h"
48
49 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
50
51 struct {
52         const char *cm_opt;
53         const char *oc_opt;
54         char       has_value;
55 } oc_options[] = {
56         { "OpenConnect.NoCertCheck", "--no-cert-check", 0 },
57 };
58
59 struct oc_private_data {
60         struct vpn_provider *provider;
61         struct connman_task *task;
62         char *if_name;
63         vpn_provider_connect_cb_t cb;
64         void *user_data;
65 };
66
67 static void free_private_data(struct oc_private_data *data)
68 {
69         g_free(data->if_name);
70         g_free(data);
71 }
72
73 static int task_append_config_data(struct vpn_provider *provider,
74                                         struct connman_task *task)
75 {
76         const char *option;
77         int i;
78
79         for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) {
80                 if (!oc_options[i].oc_opt)
81                         continue;
82
83                 option = vpn_provider_get_string(provider,
84                                         oc_options[i].cm_opt);
85                 if (!option)
86                         continue;
87
88                 if (connman_task_add_argument(task,
89                                 oc_options[i].oc_opt,
90                                 oc_options[i].has_value ? option : NULL) < 0)
91                         return -EIO;
92         }
93
94         return 0;
95 }
96
97 static int oc_notify(DBusMessage *msg, struct vpn_provider *provider)
98 {
99         DBusMessageIter iter, dict;
100         const char *reason, *key, *value;
101         char *domain = NULL;
102         char *addressv4 = NULL, *addressv6 = NULL;
103         char *netmask = NULL, *gateway = NULL;
104         unsigned char prefix_len = 0;
105         struct connman_ipaddress *ipaddress;
106
107         dbus_message_iter_init(msg, &iter);
108
109         dbus_message_iter_get_basic(&iter, &reason);
110         dbus_message_iter_next(&iter);
111
112         if (!provider) {
113                 connman_error("No provider found");
114                 return VPN_STATE_FAILURE;
115         }
116
117         if (strcmp(reason, "connect"))
118                 return VPN_STATE_DISCONNECT;
119
120         domain = g_strdup(vpn_provider_get_string(provider, "VPN.Domain"));
121
122         dbus_message_iter_recurse(&iter, &dict);
123
124         while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
125                 DBusMessageIter entry;
126
127                 dbus_message_iter_recurse(&dict, &entry);
128                 dbus_message_iter_get_basic(&entry, &key);
129                 dbus_message_iter_next(&entry);
130                 dbus_message_iter_get_basic(&entry, &value);
131
132                 if (strcmp(key, "CISCO_CSTP_OPTIONS"))
133                         DBG("%s = %s", key, value);
134
135                 if (!strcmp(key, "VPNGATEWAY"))
136                         gateway = g_strdup(value);
137
138                 if (!strcmp(key, "INTERNAL_IP4_ADDRESS"))
139                         addressv4 = g_strdup(value);
140
141                 if (!strcmp(key, "INTERNAL_IP6_ADDRESS")) {
142                         addressv6 = g_strdup(value);
143                         prefix_len = 128;
144                 }
145
146                 if (!strcmp(key, "INTERNAL_IP4_NETMASK"))
147                         netmask = g_strdup(value);
148
149                 if (!strcmp(key, "INTERNAL_IP6_NETMASK")) {
150                         char *sep;
151
152                         /* The netmask contains the address and the prefix */
153                         sep = strchr(value, '/');
154                         if (sep) {
155                                 unsigned char ip_len = sep - value;
156
157                                 addressv6 = g_strndup(value, ip_len);
158                                 prefix_len = (unsigned char)
159                                                 strtol(sep + 1, NULL, 10);
160                         }
161                 }
162
163                 if (!strcmp(key, "INTERNAL_IP4_DNS") ||
164                                 !strcmp(key, "INTERNAL_IP6_DNS"))
165                         vpn_provider_set_nameservers(provider, value);
166
167                 if (!strcmp(key, "CISCO_PROXY_PAC"))
168                         vpn_provider_set_pac(provider, value);
169
170                 if (!domain && !strcmp(key, "CISCO_DEF_DOMAIN")) {
171                         g_free(domain);
172                         domain = g_strdup(value);
173                 }
174
175                 if (g_str_has_prefix(key, "CISCO_SPLIT_INC") ||
176                         g_str_has_prefix(key, "CISCO_IPV6_SPLIT_INC"))
177                         vpn_provider_append_route(provider, key, value);
178
179                 dbus_message_iter_next(&dict);
180         }
181
182         DBG("%p %p", addressv4, addressv6);
183
184         if (addressv4)
185                 ipaddress = connman_ipaddress_alloc(AF_INET);
186         else if (addressv6)
187                 ipaddress = connman_ipaddress_alloc(AF_INET6);
188         else
189                 ipaddress = NULL;
190
191         if (!ipaddress) {
192                 g_free(addressv4);
193                 g_free(addressv6);
194                 g_free(netmask);
195                 g_free(gateway);
196                 g_free(domain);
197
198                 return VPN_STATE_FAILURE;
199         }
200
201         if (addressv4)
202                 connman_ipaddress_set_ipv4(ipaddress, addressv4,
203                                                 netmask, gateway);
204         else
205                 connman_ipaddress_set_ipv6(ipaddress, addressv6,
206                                                 prefix_len, gateway);
207         vpn_provider_set_ipaddress(provider, ipaddress);
208         vpn_provider_set_domain(provider, domain);
209
210         g_free(addressv4);
211         g_free(addressv6);
212         g_free(netmask);
213         g_free(gateway);
214         g_free(domain);
215         connman_ipaddress_free(ipaddress);
216
217         return VPN_STATE_CONNECT;
218 }
219
220 static int run_connect(struct vpn_provider *provider,
221                         struct connman_task *task, const char *if_name,
222                         vpn_provider_connect_cb_t cb, void *user_data)
223 {
224         const char *vpnhost, *vpncookie, *servercert, *mtu;
225         int fd, err = 0, len;
226
227         vpnhost = vpn_provider_get_string(provider, "OpenConnect.VPNHost");
228         if (!vpnhost)
229                 vpnhost = vpn_provider_get_string(provider, "Host");
230         vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie");
231         servercert = vpn_provider_get_string(provider,
232                         "OpenConnect.ServerCert");
233
234         if (!vpncookie || !servercert) {
235                 err = -EINVAL;
236                 goto done;
237         }
238
239         task_append_config_data(provider, task);
240
241         connman_task_add_argument(task, "--servercert", servercert);
242
243         mtu = vpn_provider_get_string(provider, "VPN.MTU");
244
245         if (mtu)
246                 connman_task_add_argument(task, "--mtu", (char *)mtu);
247
248         connman_task_add_argument(task, "--syslog", NULL);
249         connman_task_add_argument(task, "--cookie-on-stdin", NULL);
250
251         connman_task_add_argument(task, "--script",
252                                   SCRIPTDIR "/openconnect-script");
253
254         connman_task_add_argument(task, "--interface", if_name);
255
256         connman_task_add_argument(task, (char *)vpnhost, NULL);
257
258         err = connman_task_run(task, vpn_died, provider,
259                                &fd, NULL, NULL);
260         if (err < 0) {
261                 connman_error("openconnect failed to start");
262                 err = -EIO;
263                 goto done;
264         }
265
266         len = strlen(vpncookie);
267         if (write(fd, vpncookie, len) != (ssize_t)len ||
268                         write(fd, "\n", 1) != 1) {
269                 connman_error("openconnect failed to take cookie on stdin");
270                 err = -EIO;
271                 goto done;
272         }
273
274 done:
275         if (cb)
276                 cb(provider, user_data, err);
277
278         return err;
279 }
280
281 static void request_input_append_informational(DBusMessageIter *iter,
282                 void *user_data)
283 {
284         const char *str;
285
286         str = "string";
287         connman_dbus_dict_append_basic(iter, "Type", DBUS_TYPE_STRING, &str);
288
289         str = "informational";
290         connman_dbus_dict_append_basic(iter, "Requirement", DBUS_TYPE_STRING,
291                         &str);
292
293         str = user_data;
294         connman_dbus_dict_append_basic(iter, "Value", DBUS_TYPE_STRING, &str);
295 }
296
297 static void request_input_append_mandatory(DBusMessageIter *iter,
298                 void *user_data)
299 {
300         char *str = "string";
301
302         connman_dbus_dict_append_basic(iter, "Type",
303                                 DBUS_TYPE_STRING, &str);
304         str = "mandatory";
305         connman_dbus_dict_append_basic(iter, "Requirement",
306                                 DBUS_TYPE_STRING, &str);
307 }
308
309 static void request_input_cookie_reply(DBusMessage *reply, void *user_data)
310 {
311         struct oc_private_data *data = user_data;
312         char *cookie = NULL, *servercert = NULL, *vpnhost = NULL;
313         char *key;
314         DBusMessageIter iter, dict;
315
316         DBG("provider %p", data->provider);
317
318         if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR)
319                 goto err;
320
321         if (!vpn_agent_check_reply_has_dict(reply))
322                 goto err;
323
324         dbus_message_iter_init(reply, &iter);
325         dbus_message_iter_recurse(&iter, &dict);
326         while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
327                 DBusMessageIter entry, value;
328
329                 dbus_message_iter_recurse(&dict, &entry);
330                 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
331                         break;
332
333                 dbus_message_iter_get_basic(&entry, &key);
334
335                 if (g_str_equal(key, "OpenConnect.Cookie")) {
336                         dbus_message_iter_next(&entry);
337                         if (dbus_message_iter_get_arg_type(&entry)
338                                                         != DBUS_TYPE_VARIANT)
339                                 break;
340                         dbus_message_iter_recurse(&entry, &value);
341                         if (dbus_message_iter_get_arg_type(&value)
342                                                         != DBUS_TYPE_STRING)
343                                 break;
344                         dbus_message_iter_get_basic(&value, &cookie);
345                         vpn_provider_set_string_hide_value(data->provider,
346                                         key, cookie);
347
348                 } else if (g_str_equal(key, "OpenConnect.ServerCert")) {
349                         dbus_message_iter_next(&entry);
350                         if (dbus_message_iter_get_arg_type(&entry)
351                                                         != DBUS_TYPE_VARIANT)
352                                 break;
353                         dbus_message_iter_recurse(&entry, &value);
354                         if (dbus_message_iter_get_arg_type(&value)
355                                                         != DBUS_TYPE_STRING)
356                                 break;
357                         dbus_message_iter_get_basic(&value, &servercert);
358                         vpn_provider_set_string(data->provider, key,
359                                         servercert);
360
361                 } else if (g_str_equal(key, "OpenConnect.VPNHost")) {
362                         dbus_message_iter_next(&entry);
363                         if (dbus_message_iter_get_arg_type(&entry)
364                                                         != DBUS_TYPE_VARIANT)
365                                 break;
366                         dbus_message_iter_recurse(&entry, &value);
367                         if (dbus_message_iter_get_arg_type(&value)
368                                                         != DBUS_TYPE_STRING)
369                                 break;
370                         dbus_message_iter_get_basic(&value, &vpnhost);
371                         vpn_provider_set_string(data->provider, key, vpnhost);
372                 }
373
374                 dbus_message_iter_next(&dict);
375         }
376
377         if (!cookie || !servercert || !vpnhost)
378                 goto err;
379
380         run_connect(data->provider, data->task, data->if_name, data->cb,
381                 data->user_data);
382
383         free_private_data(data);
384
385         return;
386
387 err:
388         vpn_provider_indicate_error(data->provider,
389                         VPN_PROVIDER_ERROR_AUTH_FAILED);
390
391         free_private_data(data);
392 }
393
394 static int request_cookie_input(struct vpn_provider *provider,
395                                 struct oc_private_data *data,
396                                 const char *dbus_sender)
397 {
398         DBusMessage *message;
399         const char *path, *agent_sender, *agent_path;
400         DBusMessageIter iter;
401         DBusMessageIter dict;
402         const char *str;
403         int err;
404         void *agent;
405
406         agent = connman_agent_get_info(dbus_sender, &agent_sender,
407                                                         &agent_path);
408         if (!provider || !agent || !agent_path)
409                 return -ESRCH;
410
411         message = dbus_message_new_method_call(agent_sender, agent_path,
412                                         VPN_AGENT_INTERFACE,
413                                         "RequestInput");
414         if (!message)
415                 return -ENOMEM;
416
417         dbus_message_iter_init_append(message, &iter);
418
419         path = vpn_provider_get_path(provider);
420         dbus_message_iter_append_basic(&iter,
421                                 DBUS_TYPE_OBJECT_PATH, &path);
422
423         connman_dbus_dict_open(&iter, &dict);
424
425         str = vpn_provider_get_string(provider, "OpenConnect.CACert");
426         if (str)
427                 connman_dbus_dict_append_dict(&dict, "OpenConnect.CACert",
428                                 request_input_append_informational,
429                                 (void *)str);
430
431         str = vpn_provider_get_string(provider, "OpenConnect.ClientCert");
432         if (str)
433                 connman_dbus_dict_append_dict(&dict, "OpenConnect.ClientCert",
434                                 request_input_append_informational,
435                                 (void *)str);
436
437         connman_dbus_dict_append_dict(&dict, "OpenConnect.ServerCert",
438                         request_input_append_mandatory, NULL);
439
440         connman_dbus_dict_append_dict(&dict, "OpenConnect.VPNHost",
441                         request_input_append_mandatory, NULL);
442
443         connman_dbus_dict_append_dict(&dict, "OpenConnect.Cookie",
444                         request_input_append_mandatory, NULL);
445
446         vpn_agent_append_host_and_name(&dict, provider);
447
448         connman_dbus_dict_close(&iter, &dict);
449
450         err = connman_agent_queue_message(provider, message,
451                         connman_timeout_input_request(),
452                         request_input_cookie_reply, data, agent);
453
454         if (err < 0 && err != -EBUSY) {
455                 DBG("error %d sending agent request", err);
456                 dbus_message_unref(message);
457
458                 return err;
459         }
460
461         dbus_message_unref(message);
462
463         return -EINPROGRESS;
464 }
465
466 static int oc_connect(struct vpn_provider *provider,
467                         struct connman_task *task, const char *if_name,
468                         vpn_provider_connect_cb_t cb,
469                         const char *dbus_sender, void *user_data)
470 {
471         const char *vpnhost, *vpncookie, *servercert;
472         int err;
473
474         vpnhost = vpn_provider_get_string(provider, "Host");
475         if (!vpnhost) {
476                 connman_error("Host not set; cannot enable VPN");
477                 return -EINVAL;
478         }
479
480         vpncookie = vpn_provider_get_string(provider, "OpenConnect.Cookie");
481         servercert = vpn_provider_get_string(provider,
482                         "OpenConnect.ServerCert");
483         if (!vpncookie || !servercert) {
484                 struct oc_private_data *data;
485
486                 data = g_try_new0(struct oc_private_data, 1);
487                 if (!data)
488                         return -ENOMEM;
489
490                 data->provider = provider;
491                 data->task = task;
492                 data->if_name = g_strdup(if_name);
493                 data->cb = cb;
494                 data->user_data = user_data;
495
496                 err = request_cookie_input(provider, data, dbus_sender);
497                 if (err != -EINPROGRESS) {
498                         vpn_provider_indicate_error(data->provider,
499                                         VPN_PROVIDER_ERROR_LOGIN_FAILED);
500                         free_private_data(data);
501                 }
502                 return err;
503         }
504
505         return run_connect(provider, task, if_name, cb, user_data);
506 }
507
508 static int oc_save(struct vpn_provider *provider, GKeyFile *keyfile)
509 {
510         const char *setting, *option;
511         int i;
512
513         setting = vpn_provider_get_string(provider,
514                                         "OpenConnect.ServerCert");
515         if (setting)
516                 g_key_file_set_string(keyfile,
517                                 vpn_provider_get_save_group(provider),
518                                 "OpenConnect.ServerCert", setting);
519
520         setting = vpn_provider_get_string(provider,
521                                         "OpenConnect.CACert");
522         if (setting)
523                 g_key_file_set_string(keyfile,
524                                 vpn_provider_get_save_group(provider),
525                                 "OpenConnect.CACert", setting);
526
527         setting = vpn_provider_get_string(provider,
528                                         "VPN.MTU");
529         if (setting)
530                 g_key_file_set_string(keyfile,
531                                 vpn_provider_get_save_group(provider),
532                                 "VPN.MTU", setting);
533
534         for (i = 0; i < (int)ARRAY_SIZE(oc_options); i++) {
535                 if (strncmp(oc_options[i].cm_opt, "OpenConnect.", 12) == 0) {
536                         option = vpn_provider_get_string(provider,
537                                                         oc_options[i].cm_opt);
538                         if (!option)
539                                 continue;
540
541                         g_key_file_set_string(keyfile,
542                                         vpn_provider_get_save_group(provider),
543                                         oc_options[i].cm_opt, option);
544                 }
545         }
546
547         return 0;
548 }
549
550 static int oc_error_code(struct vpn_provider *provider, int exit_code)
551 {
552
553         switch (exit_code) {
554         case 1:
555         case 2:
556                 vpn_provider_set_string_hide_value(provider,
557                                 "OpenConnect.Cookie", NULL);
558                 return VPN_PROVIDER_ERROR_LOGIN_FAILED;
559         default:
560                 return VPN_PROVIDER_ERROR_UNKNOWN;
561         }
562 }
563
564 static struct vpn_driver vpn_driver = {
565         .notify         = oc_notify,
566         .connect        = oc_connect,
567         .error_code     = oc_error_code,
568         .save           = oc_save,
569 };
570
571 static int openconnect_init(void)
572 {
573         return vpn_register("openconnect", &vpn_driver, OPENCONNECT);
574 }
575
576 static void openconnect_exit(void)
577 {
578         vpn_unregister("openconnect");
579 }
580
581 CONNMAN_PLUGIN_DEFINE(openconnect, "OpenConnect VPN plugin", VERSION,
582         CONNMAN_PLUGIN_PRIORITY_DEFAULT, openconnect_init, openconnect_exit)