1 /*
2 * lws-minimal-dbus-server
3 *
4 * Written in 2010-2019 by Andy Green <[email protected]>
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
28 static struct lws_context *context;
29 static const char *version = "0.1";
30 static int interrupted;
31 static struct lws_dbus_ctx dbus_ctx, ctx_listener;
32 static 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
40 static const char *
41 server_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
81 static DBusHandlerResult
dmh_introspect(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)82 dmh_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
90 static DBusHandlerResult
dmh_get(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)91 dmh_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
117 static DBusHandlerResult
dmh_getall(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)118 dmh_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
139 static DBusHandlerResult
dmh_ping(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)140 dmh_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
150 static DBusHandlerResult
dmh_echo(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)151 dmh_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
173 static DBusHandlerResult
dmh_emit_signal(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)174 dmh_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
190 static DBusHandlerResult
dmh_emit_quit(DBusConnection * c,DBusMessage * m,DBusMessage ** reply,void * d)191 dmh_emit_quit(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
192 {
193 interrupted = 1;
194
195 return DBUS_HANDLER_RESULT_HANDLED;
196 }
197
198 struct 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
212 static DBusHandlerResult
server_message_handler(DBusConnection * conn,DBusMessage * message,void * data)213 server_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
248 static const DBusObjectPathVTable server_vtable = {
249 .message_function = server_message_handler
250 };
251
252 static void
destroy_dbus_server_conn(struct lws_dbus_ctx * ctx)253 destroy_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
265 static void
cb_closing(struct lws_dbus_ctx * ctx)266 cb_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
275 static void
new_conn(DBusServer * server,DBusConnection * conn,void * data)276 new_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
310 bail:
311 free(conn_ctx);
312 }
313
314 static int
create_dbus_listener(const char * ads)315 create_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
331 static int
create_dbus_server_conn(struct lws_dbus_ctx * ctx,DBusBusType type)332 create_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
383 fail:
384 dbus_error_free(&err);
385
386 return 1;
387 }
388
389 /*
390 * Cleanly release the connection
391 */
392
393 static void
destroy_dbus_server_listener(struct lws_dbus_ctx * ctx)394 destroy_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
417 static void
spam_connected_clients(struct lws_dbus_ctx * ctx)418 spam_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
sigint_handler(int sig)452 void sigint_handler(int sig)
453 {
454 interrupted = 1;
455 }
456
main(int argc,const char ** argv)457 int 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
529 bail:
530 lwsl_err("%s: failed to start\n", __func__);
531
532 lws_context_destroy(context);
533
534 return 1;
535 }
536