1/***
2  This file is part of PulseAudio.
3
4  Copyright 2009 Tanu Kaskinen
5
6  PulseAudio is free software; you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as published
8  by the Free Software Foundation; either version 2.1 of the License,
9  or (at your option) any later version.
10
11  PulseAudio is distributed in the hope that it will be useful, but
12  WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  General Public License for more details.
15
16  You should have received a copy of the GNU Lesser General Public License
17  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <dbus/dbus.h>
25
26#include <pulse/client-conf.h>
27#include <pulse/xmalloc.h>
28
29#include <pulsecore/core.h>
30#include <pulsecore/core-util.h>
31#include <pulsecore/dbus-shared.h>
32#include <pulsecore/macro.h>
33#include <pulsecore/protocol-dbus.h>
34
35#include "server-lookup.h"
36
37#define OBJECT_PATH "/org/pulseaudio/server_lookup1"
38#define INTERFACE "org.PulseAudio.ServerLookup1"
39
40struct pa_dbusobj_server_lookup {
41    pa_core *core;
42    pa_dbus_connection *conn;
43    bool path_registered;
44};
45
46static const char introspection[] =
47    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
48    "<node>"
49    " <!-- If you are looking for documentation make sure to check out\n"
50    "      http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/DBus/ -->\n"
51    " <interface name=\"" INTERFACE "\">\n"
52    "  <property name=\"Address\" type=\"s\" access=\"read\"/>\n"
53    " </interface>\n"
54    " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"
55    "  <method name=\"Introspect\">\n"
56    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"
57    "  </method>\n"
58    " </interface>\n"
59    " <interface name=\"" DBUS_INTERFACE_PROPERTIES "\">\n"
60    "  <method name=\"Get\">\n"
61    "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
62    "   <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
63    "   <arg name=\"value\" type=\"v\" direction=\"out\"/>\n"
64    "  </method>\n"
65    "  <method name=\"Set\">\n"
66    "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
67    "   <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
68    "   <arg name=\"value\" type=\"v\" direction=\"in\"/>\n"
69    "  </method>\n"
70    "  <method name=\"GetAll\">\n"
71    "   <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
72    "   <arg name=\"props\" type=\"a{sv}\" direction=\"out\"/>\n"
73    "  </method>\n"
74    " </interface>\n"
75    "</node>\n";
76
77static void unregister_cb(DBusConnection *conn, void *user_data) {
78    pa_dbusobj_server_lookup *sl = user_data;
79
80    pa_assert(sl);
81    pa_assert(sl->path_registered);
82
83    sl->path_registered = false;
84}
85
86static DBusHandlerResult handle_introspect(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
87    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
88    const char *i = introspection;
89    DBusMessage *reply = NULL;
90
91    pa_assert(conn);
92    pa_assert(msg);
93
94    if (!(reply = dbus_message_new_method_return(msg))) {
95        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
96        goto finish;
97    }
98    if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &i, DBUS_TYPE_INVALID)) {
99        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
100        goto finish;
101    }
102    if (!dbus_connection_send(conn, reply, NULL)) {
103        r = DBUS_HANDLER_RESULT_NEED_MEMORY;
104        goto finish;
105    }
106
107finish:
108    if (reply)
109        dbus_message_unref(reply);
110
111    return r;
112}
113
114enum get_address_result_t {
115    SUCCESS,
116    SERVER_FROM_TYPE_FAILED
117};
118
119/* Caller frees the returned address. */
120static enum get_address_result_t get_address(pa_server_type_t server_type, char **address) {
121    enum get_address_result_t r = SUCCESS;
122    pa_client_conf *conf = pa_client_conf_new();
123
124    *address = NULL;
125
126    pa_client_conf_load(conf, false, false);
127
128    if (conf->default_dbus_server)
129        *address = pa_xstrdup(conf->default_dbus_server);
130    else if (!(*address = pa_get_dbus_address_from_server_type(server_type))) {
131        r = SERVER_FROM_TYPE_FAILED;
132        goto finish;
133    }
134
135finish:
136    pa_client_conf_free(conf);
137    return r;
138}
139
140static DBusHandlerResult handle_get_address(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
141    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
142    DBusMessage *reply = NULL;
143    char *address = NULL;
144    DBusMessageIter msg_iter;
145    DBusMessageIter variant_iter;
146
147    pa_assert(conn);
148    pa_assert(msg);
149    pa_assert(sl);
150
151    switch (get_address(sl->core->server_type, &address)) {
152        case SUCCESS:
153            if (!(reply = dbus_message_new_method_return(msg))) {
154                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
155                goto finish;
156            }
157            dbus_message_iter_init_append(reply, &msg_iter);
158            if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) {
159                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
160                goto finish;
161            }
162            if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) {
163                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
164                goto finish;
165            }
166            if (!dbus_message_iter_close_container(&msg_iter, &variant_iter)) {
167                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
168                goto finish;
169            }
170            if (!dbus_connection_send(conn, reply, NULL)) {
171                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
172                goto finish;
173            }
174            r = DBUS_HANDLER_RESULT_HANDLED;
175            goto finish;
176
177        case SERVER_FROM_TYPE_FAILED:
178            if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) {
179                r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
180                goto finish;
181            }
182            if (!dbus_connection_send(conn, reply, NULL)) {
183                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
184                goto finish;
185            }
186            r = DBUS_HANDLER_RESULT_HANDLED;
187            goto finish;
188
189        default:
190            pa_assert_not_reached();
191    }
192
193finish:
194    pa_xfree(address);
195    if (reply)
196        dbus_message_unref(reply);
197
198    return r;
199}
200
201static DBusHandlerResult handle_get(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
202    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
203    const char* interface;
204    const char* property;
205    DBusMessage *reply = NULL;
206
207    pa_assert(conn);
208    pa_assert(msg);
209    pa_assert(sl);
210
211    if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
212        if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) {
213            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
214            goto finish;
215        }
216        if (!dbus_connection_send(conn, reply, NULL)) {
217            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
218            goto finish;
219        }
220        r = DBUS_HANDLER_RESULT_HANDLED;
221        goto finish;
222    }
223
224    if (*interface && !pa_streq(interface, INTERFACE)) {
225        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
226        goto finish;
227    }
228
229    if (!pa_streq(property, "Address")) {
230        if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) {
231            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
232            goto finish;
233        }
234        if (!dbus_connection_send(conn, reply, NULL)) {
235            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
236            goto finish;
237        }
238        r = DBUS_HANDLER_RESULT_HANDLED;
239        goto finish;
240    }
241
242    r = handle_get_address(conn, msg, sl);
243
244finish:
245    if (reply)
246        dbus_message_unref(reply);
247
248    return r;
249}
250
251static DBusHandlerResult handle_set(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
252    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
253    const char* interface;
254    const char* property;
255    DBusMessage *reply = NULL;
256
257    pa_assert(conn);
258    pa_assert(msg);
259    pa_assert(sl);
260
261    if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
262        if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) {
263            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
264            goto finish;
265        }
266        if (!dbus_connection_send(conn, reply, NULL)) {
267            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
268            goto finish;
269        }
270        r = DBUS_HANDLER_RESULT_HANDLED;
271        goto finish;
272    }
273
274    if (*interface && !pa_streq(interface, INTERFACE)) {
275        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
276        goto finish;
277    }
278
279    if (!pa_streq(property, "Address")) {
280        if (!(reply = dbus_message_new_error_printf(msg, PA_DBUS_ERROR_NO_SUCH_PROPERTY, "%s: No such property", property))) {
281            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
282            goto finish;
283        }
284        if (!dbus_connection_send(conn, reply, NULL)) {
285            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
286            goto finish;
287        }
288        r = DBUS_HANDLER_RESULT_HANDLED;
289        goto finish;
290    }
291
292    if (!(reply = dbus_message_new_error_printf(msg, DBUS_ERROR_ACCESS_DENIED, "%s: Property not settable", property))) {
293        r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
294        goto finish;
295    }
296    if (!dbus_connection_send(conn, reply, NULL)) {
297        r = DBUS_HANDLER_RESULT_NEED_MEMORY;
298        goto finish;
299    }
300    r = DBUS_HANDLER_RESULT_HANDLED;
301
302finish:
303    if (reply)
304        dbus_message_unref(reply);
305
306    return r;
307}
308
309static DBusHandlerResult handle_get_all(DBusConnection *conn, DBusMessage *msg, pa_dbusobj_server_lookup *sl) {
310    DBusHandlerResult r = DBUS_HANDLER_RESULT_HANDLED;
311    DBusMessage *reply = NULL;
312    const char *property = "Address";
313    char *interface = NULL;
314    char *address = NULL;
315    DBusMessageIter msg_iter;
316    DBusMessageIter dict_iter;
317    DBusMessageIter dict_entry_iter;
318    DBusMessageIter variant_iter;
319
320    pa_assert(conn);
321    pa_assert(msg);
322    pa_assert(sl);
323
324    if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID)) {
325        if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, "Invalid arguments"))) {
326            r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
327            goto finish;
328        }
329        if (!dbus_connection_send(conn, reply, NULL)) {
330            r = DBUS_HANDLER_RESULT_NEED_MEMORY;
331            goto finish;
332        }
333        r = DBUS_HANDLER_RESULT_HANDLED;
334        goto finish;
335    }
336
337    switch (get_address(sl->core->server_type, &address)) {
338        case SUCCESS:
339            if (!(reply = dbus_message_new_method_return(msg))) {
340                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
341                goto finish;
342            }
343            dbus_message_iter_init_append(reply, &msg_iter);
344            if (!dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter)) {
345                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
346                goto finish;
347            }
348            if (!dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter)) {
349                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
350                goto finish;
351            }
352            if (!dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &property)) {
353                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
354                goto finish;
355            }
356            if (!dbus_message_iter_open_container(&dict_entry_iter, DBUS_TYPE_VARIANT, "s", &variant_iter)) {
357                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
358                goto finish;
359            }
360            if (!dbus_message_iter_append_basic(&variant_iter, DBUS_TYPE_STRING, &address)) {
361                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
362                goto finish;
363            }
364            if (!dbus_message_iter_close_container(&dict_entry_iter, &variant_iter)) {
365                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
366                goto finish;
367            }
368            if (!dbus_message_iter_close_container(&dict_iter, &dict_entry_iter)) {
369                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
370                goto finish;
371            }
372            if (!dbus_message_iter_close_container(&msg_iter, &dict_iter)) {
373                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
374                goto finish;
375            }
376            if (!dbus_connection_send(conn, reply, NULL)) {
377                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
378                goto finish;
379            }
380            r = DBUS_HANDLER_RESULT_HANDLED;
381            goto finish;
382
383        case SERVER_FROM_TYPE_FAILED:
384            if (!(reply = dbus_message_new_error(msg, DBUS_ERROR_FAILED, "PulseAudio internal error: get_dbus_server_from_type() failed."))) {
385                r = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
386                goto finish;
387            }
388            if (!dbus_connection_send(conn, reply, NULL)) {
389                r = DBUS_HANDLER_RESULT_NEED_MEMORY;
390                goto finish;
391            }
392            r = DBUS_HANDLER_RESULT_HANDLED;
393            goto finish;
394
395        default:
396            pa_assert_not_reached();
397    }
398
399finish:
400    pa_xfree(address);
401    if (reply)
402        dbus_message_unref(reply);
403
404    return r;
405}
406
407static DBusHandlerResult message_cb(DBusConnection *conn, DBusMessage *msg, void *user_data) {
408    pa_dbusobj_server_lookup *sl = user_data;
409
410    pa_assert(conn);
411    pa_assert(msg);
412    pa_assert(sl);
413
414    /* pa_log("Got message! type = %s   path = %s   iface = %s   member = %s   dest = %s", dbus_message_type_to_string(dbus_message_get_type(msg)), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), dbus_message_get_destination(msg)); */
415
416    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL)
417        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
418
419    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, "Introspect") ||
420        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Introspect")))
421        return handle_introspect(conn, msg, sl);
422
423    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Get") ||
424        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Get")))
425        return handle_get(conn, msg, sl);
426
427    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "Set") ||
428        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "Set")))
429        return handle_set(conn, msg, sl);
430
431    if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, "GetAll") ||
432        (!dbus_message_get_interface(msg) && dbus_message_has_member(msg, "GetAll")))
433        return handle_get_all(conn, msg, sl);
434
435    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
436}
437
438static DBusObjectPathVTable vtable = {
439    .unregister_function = unregister_cb,
440    .message_function = message_cb,
441    .dbus_internal_pad1 = NULL,
442    .dbus_internal_pad2 = NULL,
443    .dbus_internal_pad3 = NULL,
444    .dbus_internal_pad4 = NULL
445};
446
447pa_dbusobj_server_lookup *pa_dbusobj_server_lookup_new(pa_core *c) {
448    pa_dbusobj_server_lookup *sl;
449    DBusError error;
450
451    dbus_error_init(&error);
452
453    sl = pa_xnew(pa_dbusobj_server_lookup, 1);
454    sl->core = c;
455    sl->path_registered = false;
456
457    if (!(sl->conn = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) {
458        pa_log_warn("Unable to contact D-Bus: %s: %s", error.name, error.message);
459        goto fail;
460    }
461
462    if (!dbus_connection_register_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH, &vtable, sl)) {
463        pa_log("dbus_connection_register_object_path() failed for " OBJECT_PATH ".");
464        goto fail;
465    }
466
467    sl->path_registered = true;
468
469    return sl;
470
471fail:
472    dbus_error_free(&error);
473
474    pa_dbusobj_server_lookup_free(sl);
475
476    return NULL;
477}
478
479void pa_dbusobj_server_lookup_free(pa_dbusobj_server_lookup *sl) {
480    pa_assert(sl);
481
482    if (sl->path_registered) {
483        pa_assert(sl->conn);
484        if (!dbus_connection_unregister_object_path(pa_dbus_connection_get(sl->conn), OBJECT_PATH))
485            pa_log_debug("dbus_connection_unregister_object_path() failed for " OBJECT_PATH ".");
486    }
487
488    if (sl->conn)
489        pa_dbus_connection_unref(sl->conn);
490
491    pa_xfree(sl);
492}
493