1 /*
2  * notification_proxy.c
3  * com.apple.mobile.notification_proxy service implementation.
4  *
5  * Copyright (c) 2009 Nikias Bassen, All Rights Reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <string.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <plist/plist.h>
29 
30 #include "notification_proxy.h"
31 #include "property_list_service.h"
32 #include "common/debug.h"
33 
34 #ifdef WIN32
35 #define sleep(x) Sleep(x*1000)
36 #endif
37 
38 struct np_thread {
39 	np_client_t client;
40 	np_notify_cb_t cbfunc;
41 	void *user_data;
42 };
43 
44 /**
45  * Locks a notification_proxy client, used for thread safety.
46  *
47  * @param client notification_proxy client to lock
48  */
np_lock(np_client_t client)49 static void np_lock(np_client_t client)
50 {
51 	debug_info("Locked");
52 	mutex_lock(&client->mutex);
53 }
54 
55 /**
56  * Unlocks a notification_proxy client, used for thread safety.
57  *
58  * @param client notification_proxy client to unlock
59  */
np_unlock(np_client_t client)60 static void np_unlock(np_client_t client)
61 {
62 	debug_info("Unlocked");
63 	mutex_unlock(&client->mutex);
64 }
65 
66 /**
67  * Convert a property_list_service_error_t value to an np_error_t value.
68  * Used internally to get correct error codes.
69  *
70  * @param err A property_list_service_error_t error code
71  *
72  * @return A matching np_error_t error code,
73  *     NP_E_UNKNOWN_ERROR otherwise.
74  */
np_error(property_list_service_error_t err)75 static np_error_t np_error(property_list_service_error_t err)
76 {
77 	switch (err) {
78 		case PROPERTY_LIST_SERVICE_E_SUCCESS:
79 			return NP_E_SUCCESS;
80 		case PROPERTY_LIST_SERVICE_E_INVALID_ARG:
81 			return NP_E_INVALID_ARG;
82 		case PROPERTY_LIST_SERVICE_E_PLIST_ERROR:
83 			return NP_E_PLIST_ERROR;
84 		case PROPERTY_LIST_SERVICE_E_MUX_ERROR:
85 			return NP_E_CONN_FAILED;
86 		default:
87 			break;
88 	}
89 	return NP_E_UNKNOWN_ERROR;
90 }
91 
np_client_new(idevice_t device,lockdownd_service_descriptor_t service,np_client_t * client)92 LIBIMOBILEDEVICE_API np_error_t np_client_new(idevice_t device, lockdownd_service_descriptor_t service, np_client_t *client)
93 {
94 	property_list_service_client_t plistclient = NULL;
95 	np_error_t err = np_error(property_list_service_client_new(device, service, &plistclient));
96 	if (err != NP_E_SUCCESS) {
97 		return err;
98 	}
99 
100 	np_client_t client_loc = (np_client_t) malloc(sizeof(struct np_client_private));
101 	client_loc->parent = plistclient;
102 
103 	mutex_init(&client_loc->mutex);
104 	client_loc->notifier = THREAD_T_NULL;
105 
106 	*client = client_loc;
107 	return NP_E_SUCCESS;
108 }
109 
np_client_start_service(idevice_t device,np_client_t * client,const char * label)110 LIBIMOBILEDEVICE_API np_error_t np_client_start_service(idevice_t device, np_client_t* client, const char* label)
111 {
112 	np_error_t err = NP_E_UNKNOWN_ERROR;
113 	service_client_factory_start_service(device, NP_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(np_client_new), &err);
114 	return err;
115 }
116 
np_client_free(np_client_t client)117 LIBIMOBILEDEVICE_API np_error_t np_client_free(np_client_t client)
118 {
119 	plist_t dict;
120 	property_list_service_client_t parent;
121 
122 	if (!client)
123 		return NP_E_INVALID_ARG;
124 
125 	dict = plist_new_dict();
126 	plist_dict_set_item(dict,"Command", plist_new_string("Shutdown"));
127 	property_list_service_send_xml_plist(client->parent, dict);
128 	plist_free(dict);
129 
130 	parent = client->parent;
131 	/* notifies the client->notifier thread that it should terminate */
132 	client->parent = NULL;
133 
134 	if (client->notifier) {
135 		debug_info("joining np callback");
136 		thread_join(client->notifier);
137 		thread_free(client->notifier);
138 		client->notifier = THREAD_T_NULL;
139 	} else {
140 		dict = NULL;
141 		property_list_service_receive_plist(parent, &dict);
142 		if (dict) {
143 #ifndef STRIP_DEBUG_CODE
144 			char *cmd_value = NULL;
145 			plist_t cmd_value_node = plist_dict_get_item(dict, "Command");
146 			if (plist_get_node_type(cmd_value_node) == PLIST_STRING) {
147 				plist_get_string_val(cmd_value_node, &cmd_value);
148 			}
149 			if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) {
150 				// this is the expected answer
151 			} else {
152 				debug_info("Did not get ProxyDeath but:");
153 				debug_plist(dict);
154 			}
155 			if (cmd_value) {
156 				free(cmd_value);
157 			}
158 #endif
159 			plist_free(dict);
160 		}
161 	}
162 
163 	property_list_service_client_free(parent);
164 
165 	mutex_destroy(&client->mutex);
166 	free(client);
167 
168 	return NP_E_SUCCESS;
169 }
170 
np_post_notification(np_client_t client,const char * notification)171 LIBIMOBILEDEVICE_API np_error_t np_post_notification(np_client_t client, const char *notification)
172 {
173 	if (!client || !notification) {
174 		return NP_E_INVALID_ARG;
175 	}
176 	np_lock(client);
177 
178 	plist_t dict = plist_new_dict();
179 	plist_dict_set_item(dict,"Command", plist_new_string("PostNotification"));
180 	plist_dict_set_item(dict,"Name", plist_new_string(notification));
181 
182 	np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict));
183 	plist_free(dict);
184 
185 	if (res != NP_E_SUCCESS) {
186 		debug_info("Error sending XML plist to device!");
187 	}
188 	np_unlock(client);
189 	return res;
190 }
191 
internal_np_observe_notification(np_client_t client,const char * notification)192 static np_error_t internal_np_observe_notification(np_client_t client, const char *notification)
193 {
194 	plist_t dict = plist_new_dict();
195 	plist_dict_set_item(dict,"Command", plist_new_string("ObserveNotification"));
196 	plist_dict_set_item(dict,"Name", plist_new_string(notification));
197 
198 	np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict));
199 	if (res != NP_E_SUCCESS) {
200 		debug_info("Error sending XML plist to device!");
201 	}
202 	plist_free(dict);
203 
204 	return res;
205 }
206 
np_observe_notification(np_client_t client,const char * notification)207 LIBIMOBILEDEVICE_API np_error_t np_observe_notification( np_client_t client, const char *notification )
208 {
209 	if (!client || !notification) {
210 		return NP_E_INVALID_ARG;
211 	}
212 	np_lock(client);
213 	np_error_t res = internal_np_observe_notification(client, notification);
214 	np_unlock(client);
215 	return res;
216 }
217 
np_observe_notifications(np_client_t client,const char ** notification_spec)218 LIBIMOBILEDEVICE_API np_error_t np_observe_notifications(np_client_t client, const char **notification_spec)
219 {
220 	int i = 0;
221 	np_error_t res = NP_E_UNKNOWN_ERROR;
222 	const char **notifications = notification_spec;
223 
224 	if (!client) {
225 		return NP_E_INVALID_ARG;
226 	}
227 
228 	if (!notifications) {
229 		return NP_E_INVALID_ARG;
230 	}
231 
232 	np_lock(client);
233 	while (notifications[i]) {
234 		res = internal_np_observe_notification(client, notifications[i]);
235 		if (res != NP_E_SUCCESS) {
236 			break;
237 		}
238 		i++;
239 	}
240 	np_unlock(client);
241 
242 	return res;
243 }
244 
245 /**
246  * Checks if a notification has been sent by the device.
247  *
248  * @param client NP to get a notification from
249  * @param notification Pointer to a buffer that will be allocated and filled
250  *  with the notification that has been received.
251  *
252  * @return 0 if a notification has been received or nothing has been received,
253  *         or a negative value if an error occurred.
254  *
255  * @note You probably want to check out np_set_notify_callback
256  * @see np_set_notify_callback
257  */
np_get_notification(np_client_t client,char ** notification)258 static int np_get_notification(np_client_t client, char **notification)
259 {
260 	int res = 0;
261 	plist_t dict = NULL;
262 
263 	if (!client || !client->parent || *notification)
264 		return -1;
265 
266 	np_lock(client);
267 
268 	property_list_service_error_t perr = property_list_service_receive_plist_with_timeout(client->parent, &dict, 500);
269 	if (perr == PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT) {
270 		debug_info("NotificationProxy: no notification received!");
271 		res = 0;
272 	} else if (perr != PROPERTY_LIST_SERVICE_E_SUCCESS) {
273 		debug_info("NotificationProxy: error %d occurred!", perr);
274 		res = perr;
275 	}
276 	if (dict) {
277 		char *cmd_value = NULL;
278 		plist_t cmd_value_node = plist_dict_get_item(dict, "Command");
279 
280 		if (plist_get_node_type(cmd_value_node) == PLIST_STRING) {
281 			plist_get_string_val(cmd_value_node, &cmd_value);
282 		}
283 
284 		if (cmd_value && !strcmp(cmd_value, "RelayNotification")) {
285 			char *name_value = NULL;
286 			plist_t name_value_node = plist_dict_get_item(dict, "Name");
287 
288 			if (plist_get_node_type(name_value_node) == PLIST_STRING) {
289 				plist_get_string_val(name_value_node, &name_value);
290 			}
291 
292 			res = -2;
293 			if (name_value_node && name_value) {
294 				*notification = name_value;
295 				debug_info("got notification %s", __func__, name_value);
296 				res = 0;
297 			}
298 		} else if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) {
299 			debug_info("NotificationProxy died!");
300 			res = -1;
301 		} else if (cmd_value) {
302 			debug_info("unknown NotificationProxy command '%s' received!", cmd_value);
303 			res = -1;
304 		} else {
305 			res = -2;
306 		}
307 		if (cmd_value) {
308 			free(cmd_value);
309 		}
310 		plist_free(dict);
311 		dict = NULL;
312 	}
313 
314 	np_unlock(client);
315 
316 	return res;
317 }
318 
319 /**
320  * Internally used thread function.
321  */
np_notifier(void * arg)322 void* np_notifier( void* arg )
323 {
324 	char *notification = NULL;
325 	struct np_thread *npt = (struct np_thread*)arg;
326 
327 	if (!npt) return NULL;
328 
329 	debug_info("starting callback.");
330 	while (npt->client->parent) {
331 		if (np_get_notification(npt->client, &notification) < 0) {
332 			npt->cbfunc("", npt->user_data);
333 			break;
334 		}
335 		if (notification) {
336 			npt->cbfunc(notification, npt->user_data);
337 			free(notification);
338 			notification = NULL;
339 		}
340 		sleep(1);
341 	}
342 	if (npt) {
343 		free(npt);
344 	}
345 
346 	return NULL;
347 }
348 
np_set_notify_callback(np_client_t client,np_notify_cb_t notify_cb,void * user_data)349 LIBIMOBILEDEVICE_API np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb, void *user_data )
350 {
351 	if (!client)
352 		return NP_E_INVALID_ARG;
353 
354 	np_error_t res = NP_E_UNKNOWN_ERROR;
355 
356 	np_lock(client);
357 	if (client->notifier) {
358 		debug_info("callback already set, removing");
359 		property_list_service_client_t parent = client->parent;
360 		client->parent = NULL;
361 		thread_join(client->notifier);
362 		thread_free(client->notifier);
363 		client->notifier = THREAD_T_NULL;
364 		client->parent = parent;
365 	}
366 
367 	if (notify_cb) {
368 		struct np_thread *npt = (struct np_thread*)malloc(sizeof(struct np_thread));
369 		if (npt) {
370 			npt->client = client;
371 			npt->cbfunc = notify_cb;
372 			npt->user_data = user_data;
373 
374 			if (thread_new(&client->notifier, np_notifier, npt) == 0) {
375 				res = NP_E_SUCCESS;
376 			}
377 		}
378 	} else {
379 		debug_info("no callback set");
380 	}
381 	np_unlock(client);
382 
383 	return res;
384 }
385