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