]>
Commit | Line | Data |
---|---|---|
be00d219 TB |
1 | /* |
2 | * Copyright (C) 2010 Tobias Brunner | |
3 | * Hochschule fuer Technik Rapperswil | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation; either version 2 of the License, or (at your | |
8 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but | |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
13 | * for more details. | |
14 | */ | |
15 | ||
16 | #include <unistd.h> | |
17 | #include <cutils/sockets.h> | |
18 | #include <cutils/properties.h> | |
e9e2a4fe | 19 | #include <signal.h> |
be00d219 TB |
20 | |
21 | #include "android_service.h" | |
22 | ||
23 | #include <daemon.h> | |
24 | #include <threading/thread.h> | |
25 | #include <processing/jobs/callback_job.h> | |
26 | ||
27 | typedef struct private_android_service_t private_android_service_t; | |
28 | ||
29 | /** | |
30 | * private data of Android service | |
31 | */ | |
32 | struct private_android_service_t { | |
33 | ||
34 | /** | |
35 | * public interface | |
36 | */ | |
37 | android_service_t public; | |
38 | ||
39 | /** | |
8b775e99 | 40 | * current IKE_SA |
be00d219 | 41 | */ |
8b775e99 | 42 | ike_sa_t *ike_sa; |
be00d219 TB |
43 | |
44 | /** | |
45 | * job that handles requests from the Android control socket | |
46 | */ | |
47 | callback_job_t *job; | |
48 | ||
49 | /** | |
50 | * android credentials | |
51 | */ | |
52 | android_creds_t *creds; | |
53 | ||
54 | /** | |
55 | * android control socket | |
56 | */ | |
57 | int control; | |
58 | ||
59 | }; | |
60 | ||
5eb9eeb1 TB |
61 | /** |
62 | * Some of the error codes defined in VpnManager.java | |
63 | */ | |
64 | typedef enum { | |
65 | /** Error code to indicate an error from authentication. */ | |
66 | VPN_ERROR_AUTH = 51, | |
67 | /** Error code to indicate the connection attempt failed. */ | |
68 | VPN_ERROR_CONNECTION_FAILED = 101, | |
69 | /** Error code to indicate an error of remote server hanging up. */ | |
70 | VPN_ERROR_REMOTE_HUNG_UP = 7, | |
71 | /** Error code to indicate an error of losing connectivity. */ | |
72 | VPN_ERROR_CONNECTION_LOST = 103, | |
73 | } android_vpn_errors_t; | |
74 | ||
94ec9adc TB |
75 | /** |
76 | * send a status code back to the Android app | |
77 | */ | |
78 | static void send_status(private_android_service_t *this, u_char code) | |
79 | { | |
80 | DBG1(DBG_CFG, "status of Android plugin changed: %d", code); | |
81 | send(this->control, &code, 1, 0); | |
82 | } | |
83 | ||
8b775e99 TB |
84 | METHOD(listener_t, ike_updown, bool, |
85 | private_android_service_t *this, ike_sa_t *ike_sa, bool up) | |
86 | { | |
7913a74c TB |
87 | /* this callback is only registered during initiation, so if the IKE_SA |
88 | * goes down we assume an authentication error */ | |
89 | if (this->ike_sa == ike_sa && !up) | |
90 | { | |
91 | send_status(this, VPN_ERROR_AUTH); | |
92 | return FALSE; | |
93 | } | |
8b775e99 TB |
94 | return TRUE; |
95 | } | |
96 | ||
97 | METHOD(listener_t, child_state_change, bool, | |
98 | private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, | |
99 | child_sa_state_t state) | |
100 | { | |
7913a74c TB |
101 | /* this callback is only registered during initiation, so we still have |
102 | * the control socket open */ | |
103 | if (this->ike_sa == ike_sa && state == CHILD_DESTROYING) | |
104 | { | |
105 | send_status(this, VPN_ERROR_CONNECTION_FAILED); | |
106 | return FALSE; | |
107 | } | |
8b775e99 TB |
108 | return TRUE; |
109 | } | |
110 | ||
e9e2a4fe TB |
111 | /** |
112 | * Callback used to shutdown the daemon | |
113 | */ | |
114 | static job_requeue_t shutdown_callback(void *data) | |
115 | { | |
116 | kill(0, SIGTERM); | |
117 | return JOB_REQUEUE_NONE; | |
118 | } | |
119 | ||
8b775e99 TB |
120 | METHOD(listener_t, child_updown, bool, |
121 | private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, | |
122 | bool up) | |
123 | { | |
7913a74c TB |
124 | if (this->ike_sa == ike_sa) |
125 | { | |
126 | if (up) | |
127 | { | |
128 | /* disable the hooks registered to catch initiation failures */ | |
129 | this->public.listener.ike_updown = NULL; | |
130 | this->public.listener.child_state_change = NULL; | |
131 | property_set("vpn.status", "ok"); | |
132 | } | |
e9e2a4fe TB |
133 | else |
134 | { | |
135 | callback_job_t *job; | |
136 | /* the control socket is closed as soon as vpn.status is set to "ok" | |
137 | * and the daemon proxy then only checks for terminated daemons to | |
138 | * detect lost connections, so... */ | |
139 | DBG1(DBG_CFG, "connection lost, raising delayed SIGTERM"); | |
140 | /* to avoid any conflicts we send the SIGTERM not directly from this | |
141 | * callback, but from a different thread. we also delay it to avoid | |
142 | * a race condition during a regular shutdown */ | |
143 | job = callback_job_create(shutdown_callback, NULL, NULL, NULL); | |
144 | charon->scheduler->schedule_job(charon->scheduler, (job_t*)job, 1); | |
145 | return FALSE; | |
146 | } | |
7913a74c | 147 | } |
8b775e99 TB |
148 | return TRUE; |
149 | } | |
150 | ||
151 | METHOD(listener_t, ike_rekey, bool, | |
152 | private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) | |
153 | { | |
154 | if (this->ike_sa == old) | |
155 | { | |
156 | this->ike_sa = new; | |
157 | } | |
158 | return TRUE; | |
159 | } | |
160 | ||
be00d219 TB |
161 | /** |
162 | * Read a string argument from the Android control socket | |
163 | */ | |
164 | static char *read_argument(int fd, u_char length) | |
165 | { | |
166 | int offset = 0; | |
167 | char *data = malloc(length + 1); | |
168 | while (offset < length) | |
169 | { | |
170 | int n = recv(fd, &data[offset], length - offset, 0); | |
171 | if (n < 0) | |
172 | { | |
173 | DBG1(DBG_CFG, "failed to read argument from Android" | |
174 | " control socket: %s", strerror(errno)); | |
175 | free(data); | |
176 | return NULL; | |
177 | } | |
178 | offset += n; | |
179 | } | |
180 | data[length] = '\0'; | |
c0914c45 | 181 | DBG3(DBG_CFG, "received argument from Android control socket: %s", data); |
be00d219 TB |
182 | return data; |
183 | } | |
184 | ||
185 | /** | |
186 | * handle the request received from the Android control socket | |
187 | */ | |
188 | static job_requeue_t initiate(private_android_service_t *this) | |
189 | { | |
190 | bool oldstate; | |
191 | int fd, i = 0; | |
192 | char *hostname = NULL, *cacert = NULL, *username = NULL, *password = NULL; | |
193 | identification_t *gateway = NULL, *user = NULL; | |
194 | ike_cfg_t *ike_cfg; | |
195 | peer_cfg_t *peer_cfg; | |
196 | child_cfg_t *child_cfg; | |
197 | traffic_selector_t *ts; | |
198 | ike_sa_t *ike_sa; | |
199 | auth_cfg_t *auth; | |
200 | lifetime_cfg_t lifetime = { | |
201 | .time = { | |
202 | .life = 10800, /* 3h */ | |
203 | .rekey = 10200, /* 2h50min */ | |
204 | .jitter = 300 /* 5min */ | |
205 | } | |
206 | }; | |
207 | ||
208 | fd = accept(this->control, NULL, 0); | |
209 | if (fd < 0) | |
210 | { | |
211 | DBG1(DBG_CFG, "accept on Android control socket failed: %s", | |
212 | strerror(errno)); | |
213 | return JOB_REQUEUE_NONE; | |
214 | } | |
94ec9adc | 215 | /* the original control socket is not used anymore */ |
be00d219 | 216 | close(this->control); |
94ec9adc | 217 | this->control = fd; |
be00d219 TB |
218 | |
219 | while (TRUE) | |
220 | { | |
221 | u_char length; | |
222 | if (recv(fd, &length, 1, 0) != 1) | |
223 | { | |
224 | DBG1(DBG_CFG, "failed to read from Android control socket: %s", | |
225 | strerror(errno)); | |
226 | return JOB_REQUEUE_NONE; | |
227 | } | |
228 | ||
229 | if (length == 0xFF) | |
230 | { /* last argument */ | |
231 | break; | |
232 | } | |
233 | else | |
234 | { | |
235 | switch (i++) | |
236 | { | |
237 | case 0: /* gateway */ | |
238 | hostname = read_argument(fd, length); | |
239 | break; | |
240 | case 1: /* CA certificate name */ | |
241 | cacert = read_argument(fd, length); | |
242 | break; | |
243 | case 2: /* username */ | |
244 | username = read_argument(fd, length); | |
245 | break; | |
246 | case 3: /* password */ | |
247 | password = read_argument(fd, length); | |
248 | break; | |
249 | } | |
250 | } | |
251 | } | |
252 | ||
253 | if (cacert) | |
254 | { | |
255 | if (!this->creds->add_certificate(this->creds, cacert)) | |
256 | { | |
257 | DBG1(DBG_CFG, "failed to load CA certificate"); | |
258 | } | |
259 | /* if this is a server cert we could use the cert subject as id | |
260 | * but we have to test first if that possible to configure */ | |
261 | } | |
262 | ||
263 | gateway = identification_create_from_string(hostname); | |
264 | DBG1(DBG_CFG, "using CA certificate, gateway identitiy '%Y'", gateway); | |
265 | ||
266 | if (username) | |
267 | { | |
268 | user = identification_create_from_string(username); | |
269 | this->creds->set_username_password(this->creds, user, password); | |
270 | } | |
271 | ||
272 | ike_cfg = ike_cfg_create(TRUE, FALSE, "0.0.0.0", IKEV2_UDP_PORT, | |
273 | hostname, IKEV2_UDP_PORT); | |
274 | ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); | |
275 | ||
276 | peer_cfg = peer_cfg_create("android", 2, ike_cfg, CERT_SEND_IF_ASKED, | |
277 | UNIQUE_REPLACE, 1, /* keyingtries */ | |
278 | 36000, 0, /* rekey 10h, reauth none */ | |
279 | 600, 600, /* jitter, over 10min */ | |
280 | TRUE, 0, /* mobike, DPD */ | |
281 | host_create_from_string("0.0.0.0", 0) /* virt */, | |
282 | NULL, FALSE, NULL, NULL); /* pool, mediation */ | |
283 | ||
284 | auth = auth_cfg_create(); | |
285 | auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); | |
286 | auth->add(auth, AUTH_RULE_IDENTITY, user); | |
287 | peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); | |
288 | auth = auth_cfg_create(); | |
289 | auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); | |
290 | auth->add(auth, AUTH_RULE_IDENTITY, gateway); | |
291 | peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE); | |
292 | ||
293 | child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL, | |
ee26c537 | 294 | ACTION_NONE, ACTION_NONE, FALSE, 0, 0, NULL); |
be00d219 TB |
295 | child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP)); |
296 | ts = traffic_selector_create_dynamic(0, 0, 65535); | |
297 | child_cfg->add_traffic_selector(child_cfg, TRUE, ts); | |
298 | ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, "0.0.0.0", | |
299 | 0, "255.255.255.255", 65535); | |
300 | child_cfg->add_traffic_selector(child_cfg, FALSE, ts); | |
301 | peer_cfg->add_child_cfg(peer_cfg, child_cfg); | |
024dd37f TB |
302 | /* get an additional reference because initiate consumes one */ |
303 | child_cfg->get_ref(child_cfg); | |
be00d219 | 304 | |
7913a74c TB |
305 | /* get us an IKE_SA */ |
306 | ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager, | |
307 | peer_cfg); | |
308 | if (!ike_sa->get_peer_cfg(ike_sa)) | |
309 | { | |
310 | ike_sa->set_peer_cfg(ike_sa, peer_cfg); | |
311 | } | |
312 | peer_cfg->destroy(peer_cfg); | |
313 | ||
314 | /* store the IKE_SA so we can track its progress */ | |
315 | this->ike_sa = ike_sa; | |
be00d219 TB |
316 | |
317 | /* confirm that we received the request */ | |
94ec9adc | 318 | send_status(this, i); |
be00d219 | 319 | |
7913a74c | 320 | if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) |
be00d219 TB |
321 | { |
322 | DBG1(DBG_CFG, "failed to initiate tunnel"); | |
7913a74c TB |
323 | charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, |
324 | ike_sa); | |
94ec9adc | 325 | send_status(this, VPN_ERROR_CONNECTION_FAILED); |
be00d219 TB |
326 | return JOB_REQUEUE_NONE; |
327 | } | |
7913a74c | 328 | charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); |
be00d219 TB |
329 | return JOB_REQUEUE_NONE; |
330 | } | |
331 | ||
332 | METHOD(android_service_t, destroy, void, | |
333 | private_android_service_t *this) | |
334 | { | |
8b775e99 | 335 | charon->bus->remove_listener(charon->bus, &this->public.listener); |
94ec9adc | 336 | close(this->control); |
be00d219 TB |
337 | free(this); |
338 | } | |
339 | ||
340 | /** | |
341 | * See header | |
342 | */ | |
343 | android_service_t *android_service_create(android_creds_t *creds) | |
344 | { | |
345 | private_android_service_t *this; | |
346 | ||
347 | INIT(this, | |
348 | .public = { | |
8b775e99 TB |
349 | .listener = { |
350 | .ike_updown = _ike_updown, | |
351 | .child_state_change = _child_state_change, | |
352 | .child_updown = _child_updown, | |
353 | .ike_rekey = _ike_rekey, | |
354 | }, | |
be00d219 TB |
355 | .destroy = _destroy, |
356 | }, | |
357 | .creds = creds, | |
358 | ); | |
359 | ||
360 | this->control = android_get_control_socket("charon"); | |
361 | if (this->control == -1) | |
362 | { | |
363 | DBG1(DBG_CFG, "failed to get Android control socket"); | |
364 | free(this); | |
365 | return NULL; | |
366 | } | |
367 | ||
368 | if (listen(this->control, 1) < 0) | |
369 | { | |
370 | DBG1(DBG_CFG, "failed to listen on Android control socket: %s", | |
371 | strerror(errno)); | |
372 | close(this->control); | |
373 | free(this); | |
374 | return NULL; | |
375 | } | |
376 | ||
8b775e99 | 377 | charon->bus->add_listener(charon->bus, &this->public.listener); |
be00d219 TB |
378 | this->job = callback_job_create((callback_job_cb_t)initiate, this, |
379 | NULL, NULL); | |
380 | charon->processor->queue_job(charon->processor, (job_t*)this->job); | |
381 | ||
382 | return &this->public; | |
383 | } | |
384 |