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