diff options
Diffstat (limited to 'dbus/dbus-transport-afbus.c')
-rw-r--r-- | dbus/dbus-transport-afbus.c | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/dbus/dbus-transport-afbus.c b/dbus/dbus-transport-afbus.c new file mode 100644 index 00000000..9574bcaf --- /dev/null +++ b/dbus/dbus-transport-afbus.c @@ -0,0 +1,394 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-transport-socket.h Socket subclasses of DBusTransport + * + * Copyright (C) 212 Collabora Ltd + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include <errno.h> +#include <sys/socket.h> +#include "dbus-connection-internal.h" +#include "dbus-hash.h" +#include "dbus-transport-afbus.h" +#include "dbus-transport-socket.h" +#include "dbus-watch.h" + +static DBusHashTable *wkn_addresses_cache = NULL; + +/** + * @defgroup DBusTransportAfbus AF_BUS-based DBusTransport implementation + * @ingroup DBusInternals + * @brief Implementation details of DBusTransport on AF_BUS sockets + * + * @{ + */ + +/** + * Opaque object representing a AF_BUS-based transport. + */ +typedef struct DBusTransportAfbus DBusTransportAfbus; + +/** + * Implementation details of DBusTransportAfbus. All members are private. + */ +struct DBusTransportAfbus +{ + DBusTransportSocket base; /**< Parent instance */ +}; + +static dbus_bool_t +get_write_destination (int fd, + DBusMessage *message, + struct sockaddr_bus *sock) +{ + socklen_t addrlen; + const char *destination; + const char *sender; + + /* if the sender is the bus driver, don't set the sockaddr, just let the + * message be delivered to the peer socket + */ + sender = dbus_message_get_sender (message); + if (sender && strcmp (sender, DBUS_SERVICE_DBUS) == 0) + { + return FALSE; + } + + addrlen = sizeof(struct sockaddr_bus); + if (getsockname (fd, (struct sockaddr *) sock, &addrlen) != 0) + return FALSE; + if (addrlen != sizeof(struct sockaddr_bus)) + return FALSE; + if (sock->sbus_family != AF_BUS) + return FALSE; + + destination = dbus_message_get_destination (message); + if (destination && strcmp (destination, DBUS_SERVICE_DBUS) == 0) + { + /* a message for the bus driver */ + sock->sbus_addr.s_addr = 0; + return TRUE; + } + + if (destination != NULL && strlen (destination) > 0) + { + dbus_uint64_t peer = 0x0000000000000000ULL; + + /* If destination is a unique name, just retrieve the peer address from it */ + if (strncmp (destination, ":AF-BUS.", 8) == 0) + { + DBusString tmp; + + _dbus_string_init_const (&tmp, &destination[8]); + if (!_dbus_string_parse_uint (&tmp, 0, (unsigned long *) &peer, NULL)) + peer = 0x0000000000000000ULL; + _dbus_string_free (&tmp); + } + else + { + /* a message for a well known name */ + if (wkn_addresses_cache != NULL) + { + dbus_uint64_t *peer_pointer; + + peer_pointer = (dbus_uint64_t *) _dbus_hash_table_lookup_string (wkn_addresses_cache, destination); + if (peer_pointer != NULL) + peer = *peer_pointer; + } + } + + sock->sbus_addr.s_addr = peer; + + return TRUE; + } + else + { + /* a multicast message */ + sock->sbus_addr.s_addr = 0x0000ffffffffffffULL; + return TRUE; + } + + return FALSE; +} + +static int +afbus_write_socket (DBusTransportSocket *socket_transport, + DBusMessage *message, + const DBusString *buffer, + int start, + int len) +{ + int fd, bytes_written; + struct sockaddr_bus sock; + + if (!_dbus_transport_get_socket_fd ((DBusTransport *) socket_transport, &fd)) + { + _dbus_verbose ("Couldn't get socket's file descriptor\n"); + return -1; + } + + if (get_write_destination (fd, message, &sock)) + { + const char *data; + + /* Send the data to specific address */ + data = _dbus_string_get_const_data_len (buffer, start, len); + + bytes_written = sendto (fd, data, len, MSG_NOSIGNAL, + (struct sockaddr *) &sock, sizeof (struct sockaddr_bus)); + } + else + bytes_written = _dbus_write_socket (fd, buffer, start, len); + + return bytes_written; +} + +static int +afbus_write_socket_two (DBusTransportSocket *socket_transport, + DBusMessage *message, + const DBusString *header, + int header_start, + int header_len, + const DBusString *body, + int body_start, + int body_len) +{ + int fd, bytes_written; + struct sockaddr_bus sock; + + if (!_dbus_transport_get_socket_fd ((DBusTransport *) socket_transport, &fd)) + { + _dbus_verbose ("Couldn't get socket's file descriptor\n"); + return -1; + } + + if (get_write_destination (fd, message, &sock)) + { + struct iovec vectors[2]; + const char *data1, *data2; + struct msghdr m; + + data1 = _dbus_string_get_const_data_len (header, header_start, header_len); + if (body != NULL) + data2 = _dbus_string_get_const_data_len (body, body_start, body_len); + else + { + data2 = NULL; + body_start = body_len = 0; + } + + vectors[0].iov_base = (char *) data1; + vectors[0].iov_len = header_len; + vectors[1].iov_base = (char *) data2; + vectors[1].iov_len = body_len; + + _DBUS_ZERO(m); + m.msg_iov = vectors; + m.msg_iovlen = data2 ? 2 : 1; + m.msg_name = &sock; + m.msg_namelen = sizeof (sock); + + again: + bytes_written = sendmsg (fd, &m, MSG_NOSIGNAL); + if (bytes_written < 0 && errno == EINTR) + goto again; + } + else + bytes_written = _dbus_write_socket_two (fd, header, header_start, header_len, body, body_start, body_len); + + return bytes_written; +} + +static void +afbus_authenticated (DBusTransportSocket *socket_transport) +{ + if (socket_transport->base.is_server) + { + /* Make the client join the bus when authenticated, so that it can send + * unicast messages to peers other than the daemon */ + if (setsockopt (socket_transport->fd, SOL_BUS, BUS_JOIN_BUS, NULL, 0) != 0) + { + _dbus_verbose ("Could not join client to the bus\n"); + } + } +} + +static void +afbus_message_received (DBusTransportSocket *socket_transport, + DBusMessage *message) +{ + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_SIGNAL) + { + const char *path, *interface, *member; + + path = dbus_message_get_path (message); + interface = dbus_message_get_interface (message); + member = dbus_message_get_member (message); + + if (strcmp (path, DBUS_PATH_AFBUS) == 0 + && strcmp (interface, DBUS_INTERFACE_AFBUS) == 0 + && strcmp (member, "Forwarded") == 0) + { + char *wkn; + dbus_uint64_t peer; + dbus_uint64_t *peer_pointer; + + /* Update the cache */ + if (wkn_addresses_cache == NULL) + { + wkn_addresses_cache = _dbus_hash_table_new (DBUS_HASH_STRING, + dbus_free, + dbus_free); + if (wkn_addresses_cache == NULL) + return; + } + + dbus_message_get_args (message, NULL, + DBUS_TYPE_STRING, &wkn, + DBUS_TYPE_UINT64, &peer, + DBUS_TYPE_INVALID); + + peer_pointer = dbus_new (dbus_uint64_t, 1); + *peer_pointer = peer; + _dbus_hash_table_insert_string (wkn_addresses_cache, + _dbus_strdup (wkn), + (void *) peer_pointer); + } + } +} + +static const DBusTransportSocketVTable afbus_vtable = { + afbus_write_socket, + afbus_write_socket_two, + afbus_authenticated, + afbus_message_received +}; + +/** + * Creates a new AF_BUS-based transport for the given socket descriptor. + * + * @param fd the file descriptor. + * @param server_guid non-#NULL if this transport is on the server side of a connection + * @param address the transport's address + * @returns the new transport, or #NULL if no memory. + */ +DBusTransport* +_dbus_transport_new_for_afbus (int fd, + const DBusString *server_guid, + const DBusString *address) +{ + DBusTransportAfbus *afbus_transport; + + afbus_transport = dbus_new0 (DBusTransportAfbus, 1); + if (afbus_transport == NULL) + return NULL; + + if (!_dbus_transport_socket_init_base (&afbus_transport->base, + fd, + &afbus_vtable, + server_guid, address)) + goto failed_1; + + return (DBusTransport *) afbus_transport; + + failed_1: + dbus_free (afbus_transport); + + return NULL; +} + +/** + * Opens a AF_BUS-based transport. + * + * @param entry the address entry to try opening as a tcp transport. + * @param transport_p return location for the opened transport + * @param error error to be set + * @returns result of the attempt + */ +DBusTransportOpenResult +_dbus_transport_open_afbus (DBusAddressEntry *entry, + DBusTransport **transport_p, + DBusError *error) +{ + const char *method, *path; + DBusString address; + int fd; + struct sockaddr_bus sock_address; + + method = dbus_address_entry_get_method (entry); + if (strcmp (method, "afbus") != 0) + { + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + return DBUS_TRANSPORT_OPEN_NOT_HANDLED; + } + + path = dbus_address_entry_get_value (entry, "path"); + if (path == NULL) + { + _dbus_set_bad_address (error, "afbus", + "path or tmpdir", + NULL); + return DBUS_TRANSPORT_OPEN_BAD_ADDRESS; + } + + if (!_dbus_string_init (&address) || + !_dbus_string_append (&address, "afbus:path=") || + !_dbus_string_append (&address, path)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + + if (!_dbus_open_socket (&fd, PF_BUS, SOCK_SEQPACKET, BUS_PROTO_DBUS, error)) + goto failed_1; + + if (!_dbus_set_fd_nonblocking (fd, error)) + goto failed_2; + + sock_address.sbus_family = AF_BUS; + strcpy (sock_address.sbus_path, path); + if (connect (fd, (struct sockaddr *) &sock_address, sizeof (sock_address)) < 0) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to connect to socket %s: %s", + path, _dbus_strerror (errno)); + goto failed_2; + } + + *transport_p = _dbus_transport_new_for_afbus (fd, NULL, &address); + if (*transport_p == NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_2; + } + + _dbus_string_free (&address); + + return DBUS_TRANSPORT_OPEN_OK; + + failed_2: + _dbus_close_socket (fd, NULL); + + failed_1: + _dbus_string_free (&address); + + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; +} |