summaryrefslogtreecommitdiffstats
path: root/src/idevice.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/idevice.c')
-rw-r--r--src/idevice.c618
1 files changed, 618 insertions, 0 deletions
diff --git a/src/idevice.c b/src/idevice.c
new file mode 100644
index 0000000..c5050d5
--- /dev/null
+++ b/src/idevice.c
@@ -0,0 +1,618 @@
1/*
2 * idevice.c
3 * Device discovery and communication interface.
4 *
5 * Copyright (c) 2008 Zach C. All Rights Reserved.
6 * Copyright (c) 2009 Nikias Bassen. All Rights Reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23#include <stdlib.h>
24#include <string.h>
25#include <errno.h>
26#include <arpa/inet.h>
27
28#include <usbmuxd.h>
29#include <gnutls/gnutls.h>
30#include "idevice.h"
31#include "debug.h"
32
33static idevice_event_cb_t event_cb = NULL;
34
35static void usbmux_event_cb(const usbmuxd_event_t *event, void *user_data)
36{
37 idevice_event_t ev;
38
39 ev.event = event->event;
40 ev.uuid = event->device.uuid;
41 ev.conn_type = CONNECTION_USBMUXD;
42
43 if (event_cb) {
44 event_cb(&ev, user_data);
45 }
46}
47
48/**
49 * Register a callback function that will be called when device add/remove
50 * events occur.
51 *
52 * @param callback Callback function to call.
53 * @param user_data Application-specific data passed as parameter
54 * to the registered callback function.
55 *
56 * @return IDEVICE_E_SUCCESS on success or an error value when an error occured.
57 */
58idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data)
59{
60 event_cb = callback;
61 int res = usbmuxd_subscribe(usbmux_event_cb, user_data);
62 if (res != 0) {
63 event_cb = NULL;
64 debug_info("Error %d when subscribing usbmux event callback!", res);
65 return IDEVICE_E_UNKNOWN_ERROR;
66 }
67 return IDEVICE_E_SUCCESS;
68}
69
70/**
71 * Release the event callback function that has been registered with
72 * idevice_event_subscribe().
73 *
74 * @return IDEVICE_E_SUCCESS on success or an error value when an error occured.
75 */
76idevice_error_t idevice_event_unsubscribe()
77{
78 event_cb = NULL;
79 int res = usbmuxd_unsubscribe();
80 if (res != 0) {
81 debug_info("Error %d when unsubscribing usbmux event callback!", res);
82 return IDEVICE_E_UNKNOWN_ERROR;
83 }
84 return IDEVICE_E_SUCCESS;
85}
86
87/**
88 * Get a list of currently available devices.
89 *
90 * @param devices List of uuids of devices that are currently available.
91 * This list is terminated by a NULL pointer.
92 * @param count Number of devices found.
93 *
94 * @return IDEVICE_E_SUCCESS on success or an error value when an error occured.
95 */
96idevice_error_t idevice_get_device_list(char ***devices, int *count)
97{
98 usbmuxd_device_info_t *dev_list;
99
100 *devices = NULL;
101 *count = 0;
102
103 if (usbmuxd_get_device_list(&dev_list) < 0) {
104 debug_info("ERROR: usbmuxd is not running!\n", __func__);
105 return IDEVICE_E_NO_DEVICE;
106 }
107
108 char **newlist = NULL;
109 int i, newcount = 0;
110
111 for (i = 0; dev_list[i].handle > 0; i++) {
112 newlist = realloc(*devices, sizeof(char*) * (newcount+1));
113 newlist[newcount++] = strdup(dev_list[i].uuid);
114 *devices = newlist;
115 }
116 usbmuxd_device_list_free(&dev_list);
117
118 *count = newcount;
119 newlist = realloc(*devices, sizeof(char*) * (newcount+1));
120 newlist[newcount] = NULL;
121 *devices = newlist;
122
123 return IDEVICE_E_SUCCESS;
124}
125
126/**
127 * Free a list of device uuids.
128 *
129 * @param devices List of uuids to free.
130 *
131 * @return Always returnes IDEVICE_E_SUCCESS.
132 */
133idevice_error_t idevice_device_list_free(char **devices)
134{
135 if (devices) {
136 int i = 0;
137 while (devices[i++]) {
138 free(devices[i]);
139 }
140 free(devices);
141 }
142 return IDEVICE_E_SUCCESS;
143}
144
145/**
146 * Creates an idevice_t structure for the device specified by uuid,
147 * if the device is available.
148 *
149 * @note The resulting idevice_t structure has to be freed with
150 * idevice_free() if it is no longer used.
151 *
152 * @param device Upon calling this function, a pointer to a location of type
153 * idevice_t. On successful return, this location will be populated.
154 * @param uuid The UUID to match.
155 *
156 * @return IDEVICE_E_SUCCESS if ok, otherwise an error code.
157 */
158idevice_error_t idevice_new(idevice_t * device, const char *uuid)
159{
160 usbmuxd_device_info_t muxdev;
161 int res = usbmuxd_get_device_by_uuid(uuid, &muxdev);
162 if (res > 0) {
163 idevice_t phone = (idevice_t) malloc(sizeof(struct idevice_int));
164 phone->uuid = strdup(muxdev.uuid);
165 phone->conn_type = CONNECTION_USBMUXD;
166 phone->conn_data = (void*)muxdev.handle;
167 *device = phone;
168 return IDEVICE_E_SUCCESS;
169 }
170 /* other connection types could follow here */
171
172 return IDEVICE_E_NO_DEVICE;
173}
174
175/** Cleans up an idevice structure, then frees the structure itself.
176 * This is a library-level function; deals directly with the device to tear
177 * down relations, but otherwise is mostly internal.
178 *
179 * @param device idevice_t to free.
180 */
181idevice_error_t idevice_free(idevice_t device)
182{
183 if (!device)
184 return IDEVICE_E_INVALID_ARG;
185 idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
186
187 ret = IDEVICE_E_SUCCESS;
188
189 free(device->uuid);
190
191 if (device->conn_type == CONNECTION_USBMUXD) {
192 device->conn_data = 0;
193 }
194 if (device->conn_data) {
195 free(device->conn_data);
196 }
197 free(device);
198 return ret;
199}
200
201/**
202 * Set up a connection to the given device.
203 *
204 * @param device The device to connect to.
205 * @param port The destination port to connect to.
206 * @param connection Pointer to an idevice_connection_t that will be filled
207 * with the necessary data of the connection.
208 *
209 * @return IDEVICE_E_SUCCESS if ok, otherwise an error code.
210 */
211idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connection_t *connection)
212{
213 if (!device) {
214 return IDEVICE_E_INVALID_ARG;
215 }
216
217 if (device->conn_type == CONNECTION_USBMUXD) {
218 int sfd = usbmuxd_connect((uint32_t)(device->conn_data), port);
219 if (sfd < 0) {
220 debug_info("ERROR: Connecting to usbmuxd failed: %d (%s)", sfd, strerror(-sfd));
221 return IDEVICE_E_UNKNOWN_ERROR;
222 }
223 idevice_connection_t new_connection = (idevice_connection_t)malloc(sizeof(struct idevice_connection_int));
224 new_connection->type = CONNECTION_USBMUXD;
225 new_connection->data = (void*)sfd;
226 new_connection->ssl_data = NULL;
227 *connection = new_connection;
228 return IDEVICE_E_SUCCESS;
229 } else {
230 debug_info("Unknown connection type %d", device->conn_type);
231 }
232
233 return IDEVICE_E_UNKNOWN_ERROR;
234}
235
236/**
237 * Disconnect from the device and clean up the connection structure.
238 *
239 * @param connection The connection to close.
240 *
241 * @return IDEVICE_E_SUCCESS if ok, otherwise an error code.
242 */
243idevice_error_t idevice_disconnect(idevice_connection_t connection)
244{
245 if (!connection) {
246 return IDEVICE_E_INVALID_ARG;
247 }
248 /* shut down ssl if enabled */
249 if (connection->ssl_data) {
250 idevice_connection_disable_ssl(connection);
251 }
252 idevice_error_t result = IDEVICE_E_UNKNOWN_ERROR;
253 if (connection->type == CONNECTION_USBMUXD) {
254 usbmuxd_disconnect((int)(connection->data));
255 result = IDEVICE_E_SUCCESS;
256 } else {
257 debug_info("Unknown connection type %d", connection->type);
258 }
259 free(connection);
260 return result;
261}
262
263/**
264 * Internally used function to send raw data over the given connection.
265 */
266static idevice_error_t internal_connection_send(idevice_connection_t connection, const char *data, uint32_t len, uint32_t *sent_bytes)
267{
268 if (!connection || !data) {
269 return IDEVICE_E_INVALID_ARG;
270 }
271
272 if (connection->type == CONNECTION_USBMUXD) {
273 int res = usbmuxd_send((int)(connection->data), data, len, sent_bytes);
274 if (res < 0) {
275 debug_info("ERROR: usbmuxd_send returned %d (%s)", res, strerror(-res));
276 return IDEVICE_E_UNKNOWN_ERROR;
277 }
278 return IDEVICE_E_SUCCESS;
279 } else {
280 debug_info("Unknown connection type %d", connection->type);
281 }
282 return IDEVICE_E_UNKNOWN_ERROR;
283
284}
285
286/**
287 * Send data to a device via the given connection.
288 *
289 * @param connection The connection to send data over.
290 * @param data Buffer with data to send.
291 * @param len Size of the buffer to send.
292 * @param sent_bytes Pointer to an uint32_t that will be filled
293 * with the number of bytes actually sent.
294 *
295 * @return IDEVICE_E_SUCCESS if ok, otherwise an error code.
296 */
297idevice_error_t idevice_connection_send(idevice_connection_t connection, const char *data, uint32_t len, uint32_t *sent_bytes)
298{
299 if (!connection || !data || (connection->ssl_data && !connection->ssl_data->session)) {
300 return IDEVICE_E_INVALID_ARG;
301 }
302
303 if (connection->ssl_data) {
304 ssize_t sent = gnutls_record_send(connection->ssl_data->session, (void*)data, (size_t)len);
305 if ((uint32_t)sent == (uint32_t)len) {
306 *sent_bytes = sent;
307 return IDEVICE_E_SUCCESS;
308 }
309 *sent_bytes = 0;
310 return IDEVICE_E_SSL_ERROR;
311 }
312 return internal_connection_send(connection, data, len, sent_bytes);
313}
314
315/**
316 * Internally used function for receiving raw data over the given connection
317 * using a timeout.
318 */
319static idevice_error_t internal_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout)
320{
321 if (!connection) {
322 return IDEVICE_E_INVALID_ARG;
323 }
324
325 if (connection->type == CONNECTION_USBMUXD) {
326 int res = usbmuxd_recv_timeout((int)(connection->data), data, len, recv_bytes, timeout);
327 if (res < 0) {
328 debug_info("ERROR: usbmuxd_recv_timeout returned %d (%s)", res, strerror(-res));
329 return IDEVICE_E_UNKNOWN_ERROR;
330 }
331 return IDEVICE_E_SUCCESS;
332 } else {
333 debug_info("Unknown connection type %d", connection->type);
334 }
335 return IDEVICE_E_UNKNOWN_ERROR;
336}
337
338/**
339 * Receive data from a device via the given connection.
340 * This function will return after the given timeout even if no data has been
341 * received.
342 *
343 * @param connection The connection to receive data from.
344 * @param data Buffer that will be filled with the received data.
345 * This buffer has to be large enough to hold len bytes.
346 * @param len Buffer size or number of bytes to receive.
347 * @param recv_bytes Number of bytes actually received.
348 * @param timeout Timeout in milliseconds after which this function should
349 * return even if no data has been received.
350 *
351 * @return IDEVICE_E_SUCCESS if ok, otherwise an error code.
352 */
353idevice_error_t idevice_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout)
354{
355 if (!connection || (connection->ssl_data && !connection->ssl_data->session)) {
356 return IDEVICE_E_INVALID_ARG;
357 }
358
359 if (connection->ssl_data) {
360 ssize_t received = gnutls_record_recv(connection->ssl_data->session, (void*)data, (size_t)len);
361 if (received > 0) {
362 *recv_bytes = received;
363 return IDEVICE_E_SUCCESS;
364 }
365 *recv_bytes = 0;
366 return IDEVICE_E_SSL_ERROR;
367 }
368 return internal_connection_receive_timeout(connection, data, len, recv_bytes, timeout);
369}
370
371/**
372 * Internally used function for receiving raw data over the given connection.
373 */
374static idevice_error_t internal_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes)
375{
376 if (!connection) {
377 return IDEVICE_E_INVALID_ARG;
378 }
379
380 if (connection->type == CONNECTION_USBMUXD) {
381 int res = usbmuxd_recv((int)(connection->data), data, len, recv_bytes);
382 if (res < 0) {
383 debug_info("ERROR: usbmuxd_recv returned %d (%s)", res, strerror(-res));
384 return IDEVICE_E_UNKNOWN_ERROR;
385 }
386
387 return IDEVICE_E_SUCCESS;
388 } else {
389 debug_info("Unknown connection type %d", connection->type);
390 }
391 return IDEVICE_E_UNKNOWN_ERROR;
392}
393
394/**
395 * Receive data from a device via the given connection.
396 * This function is like idevice_connection_receive_timeout, but with a
397 * predefined reasonable timeout.
398 *
399 * @param connection The connection to receive data from.
400 * @param data Buffer that will be filled with the received data.
401 * This buffer has to be large enough to hold len bytes.
402 * @param len Buffer size or number of bytes to receive.
403 * @param recv_bytes Number of bytes actually received.
404 *
405 * @return IDEVICE_E_SUCCESS if ok, otherwise an error code.
406 */
407idevice_error_t idevice_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes)
408{
409 if (!connection || (connection->ssl_data && !connection->ssl_data->session)) {
410 return IDEVICE_E_INVALID_ARG;
411 }
412
413 if (connection->ssl_data) {
414 ssize_t received = gnutls_record_recv(connection->ssl_data->session, (void*)data, (size_t)len);
415 if (received > 0) {
416 *recv_bytes = received;
417 return IDEVICE_E_SUCCESS;
418 }
419 *recv_bytes = 0;
420 return IDEVICE_E_SSL_ERROR;
421 }
422 return internal_connection_receive(connection, data, len, recv_bytes);
423}
424
425idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle)
426{
427 if (!device)
428 return IDEVICE_E_INVALID_ARG;
429
430 if (device->conn_type == CONNECTION_USBMUXD) {
431 *handle = (uint32_t)device->conn_data;
432 return IDEVICE_E_SUCCESS;
433 } else {
434 debug_info("Unknown connection type %d", device->conn_type);
435 }
436 return IDEVICE_E_UNKNOWN_ERROR;
437}
438
439idevice_error_t idevice_get_uuid(idevice_t device, char **uuid)
440{
441 if (!device)
442 return IDEVICE_E_INVALID_ARG;
443
444 *uuid = strdup(device->uuid);
445 return IDEVICE_E_SUCCESS;
446}
447
448/**
449 * Internally used gnutls callback function for receiving encrypted data.
450 */
451static ssize_t internal_ssl_read(gnutls_transport_ptr_t transport, char *buffer, size_t length)
452{
453 int bytes = 0, pos_start_fill = 0;
454 size_t tbytes = 0;
455 int this_len = length;
456 idevice_error_t res;
457 idevice_connection_t connection = (idevice_connection_t)transport;
458 char *recv_buffer;
459
460 debug_info("pre-read client wants %zi bytes", length);
461
462 recv_buffer = (char *) malloc(sizeof(char) * this_len);
463
464 /* repeat until we have the full data or an error occurs */
465 do {
466 if ((res = internal_connection_receive(connection, recv_buffer, this_len, (uint32_t*)&bytes)) != IDEVICE_E_SUCCESS) {
467 debug_info("ERROR: idevice_connection_receive returned %d", res);
468 return res;
469 }
470 debug_info("post-read we got %i bytes", bytes);
471
472 // increase read count
473 tbytes += bytes;
474
475 // fill the buffer with what we got right now
476 memcpy(buffer + pos_start_fill, recv_buffer, bytes);
477 pos_start_fill += bytes;
478
479 if (tbytes >= length) {
480 break;
481 }
482
483 this_len = length - tbytes;
484 debug_info("re-read trying to read missing %i bytes", this_len);
485 } while (tbytes < length);
486
487 if (recv_buffer) {
488 free(recv_buffer);
489 }
490 return tbytes;
491}
492
493/**
494 * Internally used gnutls callback function for sending encrypted data.
495 */
496static ssize_t internal_ssl_write(gnutls_transport_ptr_t transport, char *buffer, size_t length)
497{
498 uint32_t bytes = 0;
499 idevice_connection_t connection = (idevice_connection_t)transport;
500 debug_info("pre-send length = %zi", length);
501 internal_connection_send(connection, buffer, length, &bytes);
502 debug_info("post-send sent %i bytes", bytes);
503 return bytes;
504}
505
506/**
507 * Internally used function for cleaning up SSL stuff.
508 */
509static void internal_ssl_cleanup(ssl_data_t ssl_data)
510{
511 if (!ssl_data)
512 return;
513
514 if (ssl_data->session) {
515 gnutls_deinit(ssl_data->session);
516 }
517 if (ssl_data->certificate) {
518 gnutls_certificate_free_credentials(ssl_data->certificate);
519 }
520}
521
522/**
523 * Enables SSL for the given connection.
524 *
525 * @param connection The connection to enable SSL for.
526 *
527 * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection
528 * is NULL or connection->ssl_data is non-NULL, or IDEVICE_E_SSL_ERROR when
529 * SSL initialization, setup, or handshake fails.
530 */
531idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection)
532{
533 if (!connection || connection->ssl_data)
534 return IDEVICE_E_INVALID_ARG;
535
536 idevice_error_t ret = IDEVICE_E_SSL_ERROR;
537 uint32_t return_me = 0;
538
539 ssl_data_t ssl_data_loc = (ssl_data_t)malloc(sizeof(struct ssl_data_int));
540
541 // Set up GnuTLS...
542 debug_info("enabling SSL mode");
543 errno = 0;
544 gnutls_global_init();
545 gnutls_certificate_allocate_credentials(&ssl_data_loc->certificate);
546 gnutls_certificate_set_x509_trust_file(ssl_data_loc->certificate, "hostcert.pem", GNUTLS_X509_FMT_PEM);
547 gnutls_init(&ssl_data_loc->session, GNUTLS_CLIENT);
548 {
549 int protocol_priority[16] = { GNUTLS_SSL3, 0 };
550 int kx_priority[16] = { GNUTLS_KX_ANON_DH, GNUTLS_KX_RSA, 0 };
551 int cipher_priority[16] = { GNUTLS_CIPHER_AES_128_CBC, GNUTLS_CIPHER_AES_256_CBC, 0 };
552 int mac_priority[16] = { GNUTLS_MAC_SHA1, GNUTLS_MAC_MD5, 0 };
553 int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
554
555 gnutls_cipher_set_priority(ssl_data_loc->session, cipher_priority);
556 gnutls_compression_set_priority(ssl_data_loc->session, comp_priority);
557 gnutls_kx_set_priority(ssl_data_loc->session, kx_priority);
558 gnutls_protocol_set_priority(ssl_data_loc->session, protocol_priority);
559 gnutls_mac_set_priority(ssl_data_loc->session, mac_priority);
560 }
561 gnutls_credentials_set(ssl_data_loc->session, GNUTLS_CRD_CERTIFICATE, ssl_data_loc->certificate); // this part is killing me.
562
563 debug_info("GnuTLS step 1...");
564 gnutls_transport_set_ptr(ssl_data_loc->session, (gnutls_transport_ptr_t)connection);
565 debug_info("GnuTLS step 2...");
566 gnutls_transport_set_push_function(ssl_data_loc->session, (gnutls_push_func) & internal_ssl_write);
567 debug_info("GnuTLS step 3...");
568 gnutls_transport_set_pull_function(ssl_data_loc->session, (gnutls_pull_func) & internal_ssl_read);
569 debug_info("GnuTLS step 4 -- now handshaking...");
570 if (errno)
571 debug_info("WARN: errno says %s before handshake!", strerror(errno));
572 return_me = gnutls_handshake(ssl_data_loc->session);
573 debug_info("GnuTLS handshake done...");
574
575 if (return_me != GNUTLS_E_SUCCESS) {
576 internal_ssl_cleanup(ssl_data_loc);
577 free(ssl_data_loc);
578 debug_info("GnuTLS reported something wrong.");
579 gnutls_perror(return_me);
580 debug_info("oh.. errno says %s", strerror(errno));
581 } else {
582 connection->ssl_data = ssl_data_loc;
583 ret = IDEVICE_E_SUCCESS;
584 debug_info("SSL mode enabled");
585 }
586 return ret;
587}
588
589/**
590 * Disable SSL for the given connection.
591 *
592 * @param connection The connection to disable SSL for.
593 *
594 * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection
595 * is NULL. This function also returns IDEVICE_E_SUCCESS when SSL is not
596 * enabled and does no further error checking on cleanup.
597 */
598idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection)
599{
600 if (!connection)
601 return IDEVICE_E_INVALID_ARG;
602 if (!connection->ssl_data) {
603 /* ignore if ssl is not enabled */
604 return IDEVICE_E_SUCCESS;
605 }
606
607 if (connection->ssl_data->session) {
608 gnutls_bye(connection->ssl_data->session, GNUTLS_SHUT_RDWR);
609 }
610 internal_ssl_cleanup(connection->ssl_data);
611 free(connection->ssl_data);
612 connection->ssl_data = NULL;
613
614 debug_info("SSL mode disabled");
615
616 return IDEVICE_E_SUCCESS;
617}
618