1/*
2 * lws-minimal-dbus-server
3 *
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 * This demonstrates a minimal session dbus server that uses the lws event loop,
10 * making it possible to integrate it with other lws features.
11 *
12 * The dbus server parts are based on "Sample code illustrating basic use of
13 * D-BUS" (presumed Public Domain) here:
14 *
15 * https://github.com/fbuihuu/samples-dbus/blob/master/dbus-server.c
16 */
17
18#include <stdbool.h>
19#include <string.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <unistd.h>
23#include <signal.h>
24
25#include <libwebsockets.h>
26#include <libwebsockets/lws-dbus.h>
27
28static struct lws_context *context;
29static const char *version = "0.1";
30static int interrupted;
31static struct lws_dbus_ctx dbus_ctx, ctx_listener;
32static char session;
33
34#define THIS_INTERFACE	 "org.libwebsockets.test"
35#define THIS_OBJECT	 "/org/libwebsockets/test"
36#define THIS_BUSNAME	 "org.libwebsockets.test"
37
38#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.test"
39
40static const char *
41server_introspection_xml =
42	DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
43	"<node>\n"
44	"  <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
45	"    <method name='Introspect'>\n"
46	"      <arg name='data' type='s' direction='out' />\n"
47	"    </method>\n"
48	"  </interface>\n"
49
50	"  <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n"
51	"    <method name='Get'>\n"
52	"      <arg name='interface' type='s' direction='in' />\n"
53	"      <arg name='property'  type='s' direction='in' />\n"
54	"      <arg name='value'     type='s' direction='out' />\n"
55	"    </method>\n"
56	"    <method name='GetAll'>\n"
57	"      <arg name='interface'  type='s'     direction='in'/>\n"
58	"      <arg name='properties' type='a{sv}' direction='out'/>\n"
59	"    </method>\n"
60	"  </interface>\n"
61
62	"  <interface name='"THIS_INTERFACE"'>\n"
63	"    <property name='Version' type='s' access='read' />\n"
64	"    <method name='Ping' >\n"
65	"      <arg type='s' direction='out' />\n"
66	"    </method>\n"
67	"    <method name='Echo'>\n"
68	"      <arg name='string' direction='in' type='s'/>\n"
69	"      <arg type='s' direction='out' />\n"
70	"    </method>\n"
71	"    <method name='EmitSignal'>\n"
72	"    </method>\n"
73	"    <method name='Quit'>\n"
74	"    </method>\n"
75	"    <signal name='OnEmitSignal'>\n"
76	"    </signal>"
77	"  </interface>\n"
78
79	"</node>\n";
80
81static DBusHandlerResult
82dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
83{
84	dbus_message_append_args(*reply, DBUS_TYPE_STRING,
85				 &server_introspection_xml, DBUS_TYPE_INVALID);
86
87	return DBUS_HANDLER_RESULT_HANDLED;
88}
89
90static DBusHandlerResult
91dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
92{
93	const char *interface, *property;
94	DBusError err;
95
96	dbus_error_init(&err);
97
98	if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface,
99					    DBUS_TYPE_STRING, &property,
100					    DBUS_TYPE_INVALID)) {
101		dbus_message_unref(*reply);
102		*reply = dbus_message_new_error(m, err.name, err.message);
103		dbus_error_free(&err);
104
105		return DBUS_HANDLER_RESULT_HANDLED;
106	}
107
108	if (strcmp(property, "Version")) /* Unknown property */
109		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
110
111	dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version,
112				 DBUS_TYPE_INVALID);
113
114	return DBUS_HANDLER_RESULT_HANDLED;
115}
116
117static DBusHandlerResult
118dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
119{
120	DBusMessageIter arr, di, iter, va;
121	const char *property = "Version";
122
123	dbus_message_iter_init_append(*reply, &iter);
124	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr);
125
126	/* Append all properties name/value pairs */
127	dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di);
128	dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property);
129	dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va);
130	dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version);
131	dbus_message_iter_close_container(&di, &va);
132	dbus_message_iter_close_container(&arr, &di);
133
134	dbus_message_iter_close_container(&iter, &arr);
135
136	return DBUS_HANDLER_RESULT_HANDLED;
137}
138
139static DBusHandlerResult
140dmh_ping(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
141{
142	const char *pong = "Pong";
143
144	dbus_message_append_args(*reply, DBUS_TYPE_STRING, &pong,
145					 DBUS_TYPE_INVALID);
146
147	return DBUS_HANDLER_RESULT_HANDLED;
148}
149
150static DBusHandlerResult
151dmh_echo(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
152{
153	const char *msg;
154	DBusError err;
155
156	dbus_error_init(&err);
157
158	if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING,
159				   &msg, DBUS_TYPE_INVALID)) {
160		dbus_message_unref(*reply);
161		*reply = dbus_message_new_error(m, err.name, err.message);
162		dbus_error_free(&err);
163
164		return DBUS_HANDLER_RESULT_HANDLED;
165	}
166
167	dbus_message_append_args(*reply, DBUS_TYPE_STRING, &msg,
168					 DBUS_TYPE_INVALID);
169
170	return DBUS_HANDLER_RESULT_HANDLED;
171}
172
173static DBusHandlerResult
174dmh_emit_signal(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
175{
176	DBusMessage *r = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE,
177					         "OnEmitSignal");
178
179	if (!r)
180		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
181
182	if (!dbus_connection_send(c, r, NULL))
183		return DBUS_HANDLER_RESULT_NEED_MEMORY;
184
185	/* and send the original empty reply after */
186
187	return DBUS_HANDLER_RESULT_HANDLED;
188}
189
190static DBusHandlerResult
191dmh_emit_quit(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
192{
193	interrupted = 1;
194
195	return DBUS_HANDLER_RESULT_HANDLED;
196}
197
198struct lws_dbus_methods {
199	const char *inter;
200	const char *call;
201	lws_dbus_message_handler handler;
202} meths[] = {
203	{ DBUS_INTERFACE_INTROSPECTABLE, "Introspect",	dmh_introspect	},
204	{ DBUS_INTERFACE_PROPERTIES,	 "Get",		dmh_get		},
205	{ DBUS_INTERFACE_PROPERTIES,	 "GetAll",	dmh_getall	},
206	{ THIS_INTERFACE,		 "Ping",	dmh_ping	},
207	{ THIS_INTERFACE,		 "Echo",	dmh_echo	},
208	{ THIS_INTERFACE,		 "EmitSignal",	dmh_emit_signal },
209	{ THIS_INTERFACE,		 "Quit",	dmh_emit_quit	},
210};
211
212static DBusHandlerResult
213server_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
214{
215	struct lws_dbus_methods *mp = meths;
216	DBusHandlerResult result;
217        DBusMessage *reply = NULL;
218	size_t n;
219
220	lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
221		  dbus_message_get_interface(message),
222		  dbus_message_get_member(message),
223		  dbus_message_get_path(message));
224
225	for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) {
226		if (dbus_message_is_method_call(message, mp->inter, mp->call)) {
227			reply = dbus_message_new_method_return(message);
228			if (!reply)
229				return DBUS_HANDLER_RESULT_NEED_MEMORY;
230
231			result = mp->handler(conn, message, &reply, data);
232
233			if (result == DBUS_HANDLER_RESULT_HANDLED &&
234			    !dbus_connection_send(conn, reply, NULL))
235				result = DBUS_HANDLER_RESULT_NEED_MEMORY;
236
237			dbus_message_unref(reply);
238
239			return result;
240		}
241
242		mp++;
243	}
244
245	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
246}
247
248static const DBusObjectPathVTable server_vtable = {
249	.message_function = server_message_handler
250};
251
252static void
253destroy_dbus_server_conn(struct lws_dbus_ctx *ctx)
254{
255	if (!ctx->conn)
256		return;
257
258	lwsl_notice("%s\n", __func__);
259
260	dbus_connection_unregister_object_path(ctx->conn, THIS_OBJECT);
261	lws_dll2_remove(&ctx->next);
262	dbus_connection_unref(ctx->conn);
263}
264
265static void
266cb_closing(struct lws_dbus_ctx *ctx)
267{
268	lwsl_err("%s: closing\n", __func__);
269	destroy_dbus_server_conn(ctx);
270
271	free(ctx);
272}
273
274
275static void
276new_conn(DBusServer *server, DBusConnection *conn, void *data)
277{
278	struct lws_dbus_ctx *conn_ctx, *ctx = (struct lws_dbus_ctx *)data;
279
280	lwsl_notice("%s: vh %s\n", __func__, lws_get_vhost_name(ctx->vh));
281
282	conn_ctx = malloc(sizeof(*conn_ctx));
283	if (!conn_ctx)
284		return;
285
286	memset(conn_ctx, 0, sizeof(*conn_ctx));
287
288	conn_ctx->tsi = ctx->tsi;
289	conn_ctx->vh = ctx->vh;
290	conn_ctx->conn = conn;
291
292	if (lws_dbus_connection_setup(conn_ctx, conn, cb_closing)) {
293		lwsl_err("%s: connection bind to lws failed\n", __func__);
294		goto bail;
295	}
296
297	if (!dbus_connection_register_object_path(conn, THIS_OBJECT,
298						  &server_vtable, conn_ctx)) {
299		lwsl_err("%s: Failed to register object path\n", __func__);
300		goto bail;
301	}
302
303	lws_dll2_add_head(&conn_ctx->next, &ctx->owner);
304
305	/* we take on responsibility for explicit close / unref with this... */
306	dbus_connection_ref(conn);
307
308	return;
309
310bail:
311	free(conn_ctx);
312}
313
314static int
315create_dbus_listener(const char *ads)
316{
317	DBusError e;
318
319        dbus_error_init(&e);
320
321	if (!lws_dbus_server_listen(&ctx_listener, ads, &e, new_conn)) {
322		lwsl_err("%s: failed\n", __func__);
323		dbus_error_free(&e);
324
325		return 1;
326	}
327
328	return 0;
329}
330
331static int
332create_dbus_server_conn(struct lws_dbus_ctx *ctx, DBusBusType type)
333{
334	DBusError err;
335	int rv;
336
337        dbus_error_init(&err);
338
339	/* connect to the daemon bus */
340	ctx->conn = dbus_bus_get(type, &err);
341	if (!ctx->conn) {
342		lwsl_err("%s: Failed to get a session DBus connection: %s\n",
343			 __func__, err.message);
344		goto fail;
345	}
346
347	/*
348	 * by default dbus will call exit() when this connection closes...
349	 * we have to shut down other things cleanly, so disable that
350	 */
351	dbus_connection_set_exit_on_disconnect(ctx->conn, 0);
352
353	rv = dbus_bus_request_name(ctx->conn, THIS_BUSNAME,
354				   DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
355	if (rv != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
356		lwsl_err("%s: Failed to request name on bus: %s\n",
357			 __func__, err.message);
358		goto fail;
359	}
360
361	if (!dbus_connection_register_object_path(ctx->conn, THIS_OBJECT,
362						  &server_vtable, NULL)) {
363		lwsl_err("%s: Failed to register object path for TestObject\n",
364			 __func__);
365		dbus_bus_release_name(ctx->conn, THIS_BUSNAME, &err);
366		goto fail;
367	}
368
369	/*
370	 * This is the part that binds the connection to lws watcher and
371	 * timeout handling provided by lws
372	 */
373
374	if (lws_dbus_connection_setup(ctx, ctx->conn, cb_closing)) {
375		lwsl_err("%s: connection bind to lws failed\n", __func__);
376		goto fail;
377	}
378
379	lwsl_notice("%s: created OK\n", __func__);
380
381	return 0;
382
383fail:
384	dbus_error_free(&err);
385
386	return 1;
387}
388
389/*
390 * Cleanly release the connection
391 */
392
393static void
394destroy_dbus_server_listener(struct lws_dbus_ctx *ctx)
395{
396	dbus_server_disconnect(ctx->dbs);
397
398	lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
399				   ctx->owner.head) {
400		struct lws_dbus_ctx *r =
401			lws_container_of(rdt, struct lws_dbus_ctx, next);
402
403		dbus_connection_close(r->conn);
404		dbus_connection_unref(r->conn);
405		free(r);
406	} lws_end_foreach_dll_safe(rdt, nx);
407
408	dbus_server_unref(ctx->dbs);
409}
410
411/*
412 * DBUS can send messages outside the usual client-initiated RPC concept.
413 *
414 * You can receive them using a message filter.
415 */
416
417static void
418spam_connected_clients(struct lws_dbus_ctx *ctx)
419{
420
421	/* send connected clients an unsolicited message */
422
423	lws_start_foreach_dll_safe(struct lws_dll2 *, rdt, nx,
424				   ctx->owner.head) {
425		struct lws_dbus_ctx *r =
426			lws_container_of(rdt, struct lws_dbus_ctx, next);
427
428
429		DBusMessage *msg;
430		const char *payload = "Unsolicited message";
431
432		msg = dbus_message_new(DBUS_NUM_MESSAGE_TYPES + 1);
433		if (!msg) {
434			lwsl_err("%s: new message failed\n", __func__);
435		}
436
437		dbus_message_append_args(msg, DBUS_TYPE_STRING, &payload,
438						 DBUS_TYPE_INVALID);
439		if (!dbus_connection_send(r->conn, msg, NULL)) {
440			lwsl_err("%s: unable to send\n", __func__);
441		}
442
443		lwsl_notice("%s\n", __func__);
444
445		dbus_message_unref(msg);
446
447	} lws_end_foreach_dll_safe(rdt, nx);
448
449}
450
451
452void sigint_handler(int sig)
453{
454	interrupted = 1;
455}
456
457int main(int argc, const char **argv)
458{
459	struct lws_context_creation_info info;
460	const char *p;
461	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
462			/* for LLL_ verbosity above NOTICE to be built into lws,
463			 * lws must have been configured and built with
464			 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
465			/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
466			/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
467			/* | LLL_DEBUG */ /* | LLL_THREAD */;
468
469	signal(SIGINT, sigint_handler);
470
471	if ((p = lws_cmdline_option(argc, argv, "-d")))
472		logs = atoi(p);
473
474	lws_set_log_level(logs, NULL);
475	lwsl_user("LWS minimal DBUS server\n");
476
477	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
478	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
479	context = lws_create_context(&info);
480	if (!context) {
481		lwsl_err("lws init failed\n");
482		return 1;
483	}
484
485	info.options |=
486		LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
487
488	dbus_ctx.tsi = 0;
489	ctx_listener.tsi = 0;
490	ctx_listener.vh = dbus_ctx.vh = lws_create_vhost(context, &info);
491	if (!dbus_ctx.vh)
492		goto bail;
493
494	session = !!lws_cmdline_option(argc, argv, "--session");
495
496	if (session) {
497		/* create the dbus connection, loosely bound to our lws vhost */
498
499		if (create_dbus_server_conn(&dbus_ctx, DBUS_BUS_SESSION))
500			goto bail;
501	} else {
502		if (create_dbus_listener(THIS_LISTEN_PATH)) {
503			lwsl_err("%s: create_dbus_listener failed\n", __func__);
504			goto bail;
505		}
506	}
507
508	/* lws event loop (default poll one) */
509
510	while (n >= 0 && !interrupted) {
511		if (!session)
512			spam_connected_clients(&ctx_listener);
513		n = lws_service(context, 0);
514	}
515
516	if (session)
517		destroy_dbus_server_conn(&dbus_ctx);
518	else
519		destroy_dbus_server_listener(&ctx_listener);
520
521	/* this is required for valgrind-cleanliness */
522	dbus_shutdown();
523	lws_context_destroy(context);
524
525	lwsl_notice("Exiting cleanly\n");
526
527	return 0;
528
529bail:
530	lwsl_err("%s: failed to start\n", __func__);
531
532	lws_context_destroy(context);
533
534	return 1;
535}
536