diff options
Diffstat (limited to 'network')
-rw-r--r-- | network/Makefile.am | 24 | ||||
-rw-r--r-- | network/bridge.c | 148 | ||||
-rw-r--r-- | network/bridge.h | 30 | ||||
-rw-r--r-- | network/common.c | 396 | ||||
-rw-r--r-- | network/common.h | 42 | ||||
-rw-r--r-- | network/connection.c | 719 | ||||
-rw-r--r-- | network/connection.h | 34 | ||||
-rw-r--r-- | network/main.c | 256 | ||||
-rw-r--r-- | network/manager.c | 855 | ||||
-rw-r--r-- | network/manager.h | 43 | ||||
-rw-r--r-- | network/network-api.txt | 199 | ||||
-rw-r--r-- | network/network.conf | 37 | ||||
-rw-r--r-- | network/server.c | 1097 | ||||
-rw-r--r-- | network/server.h | 33 | ||||
-rwxr-xr-x | network/test-network | 37 |
15 files changed, 3950 insertions, 0 deletions
diff --git a/network/Makefile.am b/network/Makefile.am new file mode 100644 index 00000000..15208f5a --- /dev/null +++ b/network/Makefile.am @@ -0,0 +1,24 @@ + +if NETWORKPLUGIN +plugindir = $(libdir)/bluetooth/plugins + +plugin_LTLIBRARIES = network.la + +network_la_SOURCES = main.c manager.h manager.c \ + server.h server.c bridge.h bridge.c \ + connection.h connection.c common.h common.c + +LDADD = $(top_builddir)/common/libhelper.a \ + @GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@ +endif + +AM_LDFLAGS = -module -avoid-version -no-undefined \ + -export-symbols-regex bluetooth_plugin_desc + +AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@ + +INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/hcid -I$(top_srcdir)/sdpd + +EXTRA_DIST = network.conf network-api.txt test-network + +MAINTAINERCLEANFILES = Makefile.in diff --git a/network/bridge.c b/network/bridge.c new file mode 100644 index 00000000..ba59f380 --- /dev/null +++ b/network/bridge.c @@ -0,0 +1,148 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <net/if.h> +#include <linux/sockios.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/bnep.h> + +#include "logging.h" +#include "bridge.h" +#include "common.h" + +static int bridge_socket = -1; +static const char *gn_bridge = NULL; +static const char *nap_bridge = NULL; + +int bridge_init(const char *gn_iface, const char *nap_iface) +{ +#if 0 + struct stat st; + + if (stat("/sys/module/bridge", &st) < 0) + return -EOPNOTSUPP; +#endif + bridge_socket = socket(AF_INET, SOCK_STREAM, 0); + if (bridge_socket < 0) { + error("Failed to open bridge socket: %s (%d)", + strerror(errno), errno); + return -errno; + } + + gn_bridge = gn_iface; + nap_bridge = nap_iface; + + return 0; +} + +void bridge_cleanup(void) +{ + close(bridge_socket); + + bridge_socket = -1; +} + +int bridge_create(int id) +{ + int err; + const char *name = bridge_get_name(id); + + err = ioctl(bridge_socket, SIOCBRADDBR, name); + if (err < 0) + return -errno; + + info("bridge %s created", name); + + return 0; +} + +int bridge_remove(int id) +{ + int err; + const char *name = bridge_get_name(id); + + err = bnep_if_down(name); + if (err < 0) + return err; + + err = ioctl(bridge_socket, SIOCBRDELBR, name); + if (err < 0) + return -errno; + + info("bridge %s removed", name); + + return 0; +} + +int bridge_add_interface(int id, const char *dev) +{ + struct ifreq ifr; + int err; + int ifindex = if_nametoindex(dev); + const char *name = bridge_get_name(id); + + if (!name) + return -EINVAL; + + if (ifindex == 0) + return -ENODEV; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_ifindex = ifindex; + + err = ioctl(bridge_socket, SIOCBRADDIF, &ifr); + if (err < 0) + return err; + + info("bridge %s: interface %s added", name, dev); + + err = bnep_if_up(name, id); + if (err < 0) + return err; + + return 0; +} + +const char *bridge_get_name(int id) +{ + if (id == BNEP_SVC_GN) + return gn_bridge; + + if (id == BNEP_SVC_NAP) + return nap_bridge; + + return NULL; +} diff --git a/network/bridge.h b/network/bridge.h new file mode 100644 index 00000000..13166520 --- /dev/null +++ b/network/bridge.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int bridge_init(const char *gn_iface, const char *nap_iface); +void bridge_cleanup(void); + +int bridge_create(int id); +int bridge_remove(int id); +int bridge_add_interface(int id, const char *dev); +const char *bridge_get_name(int id); diff --git a/network/common.c b/network/common.c new file mode 100644 index 00000000..f1dbe1c5 --- /dev/null +++ b/network/common.c @@ -0,0 +1,396 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <net/if.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/bnep.h> + +#include <glib.h> + +#include "logging.h" +#include "common.h" +#include "textfile.h" + +static int ctl; +static GSList *pids; + +#define PANU_UUID "00001115-0000-1000-8000-00805f9b34fb" +#define NAP_UUID "00001116-0000-1000-8000-00805f9b34fb" +#define GN_UUID "00001117-0000-1000-8000-00805f9b34fb" + +static struct { + const char *name; /* Friendly name */ + const char *uuid128; /* UUID 128 */ + uint16_t id; /* Service class identifier */ +} __svc[] = { + { "panu", PANU_UUID, BNEP_SVC_PANU }, + { "gn", GN_UUID, BNEP_SVC_GN }, + { "nap", NAP_UUID, BNEP_SVC_NAP }, + { NULL } +}; + +static const char *panu = NULL; +static const char *gn = NULL; +static const char *nap = NULL; + +struct bnep_data { + char *devname; + char *script; + int pid; +}; + +static gint find_devname(gconstpointer a, gconstpointer b) +{ + struct bnep_data *data = (struct bnep_data *) a; + const char *devname = b; + + return strcmp(data->devname, devname); +} + +static void script_exited(GPid pid, gint status, gpointer data) +{ + if (WIFEXITED(status)) + debug("%d exited with status %d", pid, WEXITSTATUS(status)); + else + debug("%d was killed by signal %d", pid, WTERMSIG(status)); + + g_spawn_close_pid(pid); +} + +uint16_t bnep_service_id(const char *svc) +{ + int i; + uint16_t id; + + /* Friendly service name */ + for (i = 0; __svc[i].name; i++) + if (!strcasecmp(svc, __svc[i].name)) { + return __svc[i].id; + } + + /* UUID 128 string */ + for (i = 0; __svc[i].uuid128; i++) + if (!strcasecmp(svc, __svc[i].uuid128)) { + return __svc[i].id; + } + + /* Try convert to HEX */ + id = strtol(svc, NULL, 16); + if ((id < BNEP_SVC_PANU) || (id > BNEP_SVC_GN)) + return 0; + + return id; +} + +const char *bnep_uuid(uint16_t id) +{ + int i; + + for (i = 0; __svc[i].uuid128; i++) + if (__svc[i].id == id) + return __svc[i].uuid128; + return NULL; +} + +const char *bnep_name(uint16_t id) +{ + int i; + + for (i = 0; __svc[i].name; i++) + if (__svc[i].id == id) + return __svc[i].name; + return NULL; +} + +int bnep_init(const char *panu_script, const char *gn_script, + const char *nap_script) +{ + ctl = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP); + + if (ctl < 0) { + int err = errno; + error("Failed to open control socket: %s (%d)", + strerror(err), err); + return -err; + } + + panu = panu_script; + gn = gn_script; + nap = nap_script; + return 0; +} + +int bnep_cleanup(void) +{ + close(ctl); + return 0; +} + +int bnep_kill_connection(bdaddr_t *dst) +{ + struct bnep_conndel_req req; + + memset(&req, 0, sizeof(req)); + baswap((bdaddr_t *)&req.dst, dst); + req.flags = 0; + if (ioctl(ctl, BNEPCONNDEL, &req)) { + int err = errno; + error("Failed to kill connection: %s (%d)", + strerror(err), err); + return -err; + } + return 0; +} + +int bnep_kill_all_connections(void) +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[7]; + int i, err; + + memset(&req, 0, sizeof(req)); + req.cnum = 7; + req.ci = ci; + if (ioctl(ctl, BNEPGETCONNLIST, &req)) { + err = errno; + error("Failed to get connection list: %s (%d)", + strerror(err), err); + return -err; + } + + for (i=0; i < req.cnum; i++) { + struct bnep_conndel_req del; + + memset(&del, 0, sizeof(del)); + memcpy(del.dst, ci[i].dst, ETH_ALEN); + del.flags = 0; + ioctl(ctl, BNEPCONNDEL, &del); + } + return 0; +} + +int bnep_connadd(int sk, uint16_t role, char *dev) +{ + struct bnep_connadd_req req; + + memset(&req, 0, sizeof(req)); + strncpy(req.device, dev, 16); + req.device[15] = '\0'; + req.sock = sk; + req.role = role; + if (ioctl(ctl, BNEPCONNADD, &req) < 0) { + int err = errno; + error("Failed to add device %s: %s(%d)", + dev, strerror(err), err); + return -err; + } + + strncpy(dev, req.device, 16); + return 0; +} + +static void bnep_setup(gpointer data) +{ +} + +static int bnep_exec(const char **argv) +{ + int pid; + GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH; + + if (!g_spawn_async(NULL, (char **) argv, NULL, flags, bnep_setup, NULL, + &pid, NULL)) { + error("Unable to execute %s %s", *argv[0], *argv[1]); + return -EINVAL; + } + + return pid; +} + +int bnep_if_up(const char *devname, uint16_t id) +{ + int sd, err; + struct ifreq ifr; + const char *argv[5]; + struct bnep_data *bnep = NULL; + GSList *l; + + /* Check if a script is running */ + l = g_slist_find_custom(pids, devname, find_devname); + if (l) { + bnep = l->data; + + if (bnep->script && !strcmp(bnep->script, "avahi-autoipd")) { + argv[0] = bnep->script; + argv[1] = devname; + argv[2] = "--refresh"; + argv[3] = NULL; + + bnep->pid = bnep_exec(argv); + } + } + + sd = socket(AF_INET6, SOCK_DGRAM, 0); + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, devname); + + ifr.ifr_flags |= IFF_UP; + ifr.ifr_flags |= IFF_MULTICAST; + + if ((ioctl(sd, SIOCSIFFLAGS, (caddr_t) &ifr)) < 0) { + err = errno; + error("Could not bring up %s. %s(%d)", devname, strerror(err), + err); + return -err; + } + + if (bnep) + return bnep->pid; + + bnep = g_new0(struct bnep_data, 1); + bnep->devname = g_strdup(devname); + + if (!id) + goto done; + + if (id == BNEP_SVC_PANU) + bnep->script = g_strdup(panu); + else if (id == BNEP_SVC_GN) + bnep->script = g_strdup(gn); + else + bnep->script = g_strdup(nap); + + if (!bnep->script) + goto done; + + argv[0] = bnep->script; + argv[1] = devname; + + if (!strcmp(bnep->script, "avahi-autoipd")) { + argv[2] = "--no-drop-root"; + argv[3] = "--no-chroot"; + argv[4] = NULL; + } else + argv[2] = NULL; + + bnep->pid = bnep_exec(argv); + g_child_watch_add(bnep->pid, script_exited, bnep); + +done: + pids = g_slist_append(pids, bnep); + + return bnep->pid; +} + +int bnep_if_down(const char *devname) +{ + int sd, err, pid; + struct ifreq ifr; + struct bnep_data *bnep; + GSList *l; + GSpawnFlags flags; + const char *argv[4]; + + l = g_slist_find_custom(pids, devname, find_devname); + if (!l) + return 0; + + bnep = l->data; + + if (!bnep->pid) + goto done; + + if (bnep->script && !strcmp(bnep->script, "avahi-autoipd")) { + argv[0] = bnep->script; + argv[1] = devname; + argv[2] = "--kill"; + argv[3] = NULL; + + flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH; + g_spawn_async(NULL, (char **) argv, NULL, flags, bnep_setup, + (gpointer) devname, &pid, NULL); + + goto done; + } + + /* Kill script */ + err = kill(bnep->pid, SIGTERM); + if (err < 0) + error("kill(%d, SIGTERM): %s (%d)", bnep->pid, + strerror(errno), errno); + +done: + sd = socket(AF_INET6, SOCK_DGRAM, 0); + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, devname); + + ifr.ifr_flags &= ~IFF_UP; + + /* Bring down the interface */ + ioctl(sd, SIOCSIFFLAGS, (caddr_t) &ifr); + + pids = g_slist_remove(pids, bnep); + + if (bnep->devname) + g_free(bnep->devname); + + if (bnep->script) + g_free(bnep->script); + + g_free(bnep); + + return 0; +} + +int read_remote_name(bdaddr_t *src, bdaddr_t *dst, char *buf, size_t size) +{ + char filename[PATH_MAX + 1], addr[18], *str; + + ba2str(src, addr); + create_name(filename, PATH_MAX, STORAGEDIR, addr, "names"); + + ba2str(dst, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + snprintf(buf, size, "%s", str); + free(str); + + return 0; +} diff --git a/network/common.h b/network/common.h new file mode 100644 index 00000000..e6aa90f6 --- /dev/null +++ b/network/common.h @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define MAX_PATH_LENGTH 64 /* D-Bus path */ +#define NETWORK_PATH "/org/bluez/network" + +int bnep_init(const char *panu_script, const char *gn_script, + const char *nap_script); +int bnep_cleanup(void); + +uint16_t bnep_service_id(const char *svc); +const char *bnep_uuid(uint16_t id); +const char *bnep_name(uint16_t id); + +int bnep_kill_connection(bdaddr_t *dst); +int bnep_kill_all_connections(void); + +int bnep_connadd(int sk, uint16_t role, char *dev); +int bnep_if_up(const char *devname, uint16_t id); +int bnep_if_down(const char *devname); + +int read_remote_name(bdaddr_t *src, bdaddr_t *dst, char *buf, size_t size); diff --git a/network/connection.c b/network/connection.c new file mode 100644 index 00000000..d2fd85c2 --- /dev/null +++ b/network/connection.c @@ -0,0 +1,719 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <netinet/in.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/bnep.h> +#include <bluetooth/sdp.h> + +#include <glib.h> +#include <gdbus.h> + +#include "../hcid/dbus-common.h" + +#include "logging.h" +#include "textfile.h" +#include "glib-helper.h" + +#include "error.h" +#include "common.h" +#include "connection.h" + +typedef enum { + CONNECTED, + CONNECTING, + DISCONNECTED +} conn_state; + +struct network_conn { + DBusMessage *msg; + bdaddr_t store; + bdaddr_t src; + bdaddr_t dst; + char *path; /* D-Bus path */ + char dev[16]; /* Interface name */ + char *name; /* Service Name */ + char *desc; /* Service Description*/ + uint16_t id; /* Role: Service Class Identifier */ + conn_state state; + int sk; +}; + +struct __service_16 { + uint16_t dst; + uint16_t src; +} __attribute__ ((packed)); + +static DBusConnection *connection = NULL; +static const char *prefix = NULL; +static GSList *connections = NULL; + +gint find_connection(gconstpointer a, gconstpointer b) +{ + const struct network_conn *nc = a; + const char *path = b; + + return strcmp(nc->path, path); +} + +static inline DBusMessage *not_supported(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Not suported"); +} + +static inline DBusMessage *already_connected(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Device already connected"); +} + +static inline DBusMessage *not_connected(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Device not connected"); +} + +static inline DBusMessage *no_pending_connect(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Device has no pending connect"); +} + +static inline DBusMessage *connection_attempt_failed(DBusMessage *msg, int err) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".ConnectionAttemptFailed", + err ? strerror(err) : "Connection attempt failed"); +} + +static gboolean bnep_watchdog_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct network_conn *nc = data; + + if (connection != NULL) { + g_dbus_emit_signal(connection, nc->path, + NETWORK_CONNECTION_INTERFACE, + "Disconnected", + DBUS_TYPE_INVALID); + } + + info("%s disconnected", nc->dev); + + bnep_if_down(nc->dev); + nc->state = DISCONNECTED; + memset(nc->dev, 0, 16); + strncpy(nc->dev, prefix, strlen(prefix)); + g_io_channel_close(chan); + + return FALSE; +} + +static gboolean bnep_connect_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct network_conn *nc = data; + struct bnep_control_rsp *rsp; + char pkt[BNEP_MTU]; + gsize r; + int sk; + DBusMessage *reply; + const char *pdev; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + error("Hangup or error on l2cap server socket"); + goto failed; + } + + memset(pkt, 0, BNEP_MTU); + if (g_io_channel_read(chan, pkt, sizeof(pkt) - 1, + &r) != G_IO_ERROR_NONE) { + error("IO Channel read error"); + goto failed; + } + + if (r <= 0) { + error("No packet received on l2cap socket"); + goto failed; + } + + errno = EPROTO; + + if (r < sizeof(*rsp)) { + error("Packet received is not bnep type"); + goto failed; + } + + rsp = (void *) pkt; + if (rsp->type != BNEP_CONTROL) { + error("Packet received is not bnep type"); + goto failed; + } + + if (rsp->ctrl != BNEP_SETUP_CONN_RSP) + return TRUE; + + r = ntohs(rsp->resp); + + if (r != BNEP_SUCCESS) { + error("bnep failed"); + goto failed; + } + + sk = g_io_channel_unix_get_fd(chan); + + if (bnep_connadd(sk, BNEP_SVC_PANU, nc->dev)) { + error("%s could not be added", nc->dev); + goto failed; + } + + bnep_if_up(nc->dev, nc->id); + g_dbus_emit_signal(connection, nc->path, + NETWORK_CONNECTION_INTERFACE, + "Connected", + DBUS_TYPE_INVALID); + + pdev = nc->dev; + + reply = g_dbus_create_reply(nc->msg, DBUS_TYPE_STRING, &pdev, + DBUS_TYPE_INVALID); + g_dbus_send_message(connection, reply); + + nc->state = CONNECTED; + + info("%s connected", nc->dev); + /* Start watchdog */ + g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) bnep_watchdog_cb, nc); + return FALSE; + +failed: + if (nc->state != DISCONNECTED) { + nc->state = DISCONNECTED; + reply = connection_attempt_failed(nc->msg, EIO); + g_dbus_send_message(connection, reply); + g_io_channel_close(chan); + } + + return FALSE; +} + +static int bnep_connect(struct network_conn *nc) +{ + struct bnep_setup_conn_req *req; + struct __service_16 *s; + unsigned char pkt[BNEP_MTU]; + GIOChannel *io; + int err = 0; + + /* Send request */ + req = (void *) pkt; + req->type = BNEP_CONTROL; + req->ctrl = BNEP_SETUP_CONN_REQ; + req->uuid_size = 2; /* 16bit UUID */ + s = (void *) req->service; + s->dst = htons(nc->id); + s->src = htons(BNEP_SVC_PANU); + + io = g_io_channel_unix_new(nc->sk); + g_io_channel_set_close_on_unref(io, FALSE); + + if (send(nc->sk, pkt, sizeof(*req) + sizeof(*s), 0) < 0) { + err = -errno; + goto out; + } + + g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) bnep_connect_cb, nc); + +out: + g_io_channel_unref(io); + return err; +} + +static void connect_cb(GIOChannel *chan, int err, const bdaddr_t *src, + const bdaddr_t *dst, gpointer data) +{ + struct network_conn *nc = data; + DBusMessage *reply; + + if (err < 0) { + error("l2cap connect(): %s (%d)", strerror(-err), -err); + goto failed; + } + + nc->sk = g_io_channel_unix_get_fd(chan); + + err = bnep_connect(nc); + if (err < 0) { + error("bnep connect(): %s (%d)", strerror(-err), -err); + g_io_channel_close(chan); + g_io_channel_unref(chan); + goto failed; + } + + return; + +failed: + nc->state = DISCONNECTED; + + reply = connection_attempt_failed(nc->msg, -err); + g_dbus_send_message(connection, reply); +} + +static DBusMessage *get_adapter(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct network_conn *nc = data; + char addr[18]; + const char *paddr = addr; + + ba2str(&nc->src, addr); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); +} + +static DBusMessage *get_address(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct network_conn *nc = data; + char addr[18]; + const char *paddr = addr; + + ba2str(&nc->dst, addr); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); +} + +static DBusMessage *get_uuid(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + const char *uuid; + + uuid = bnep_uuid(nc->id); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_INVALID); +} + +static DBusMessage *get_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + + if (!nc->name) + return not_supported(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &nc->name, + DBUS_TYPE_INVALID); +} + +static DBusMessage *get_description(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + + if (!nc->desc) + return not_supported(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &nc->desc, + DBUS_TYPE_INVALID); +} + +static DBusMessage *get_interface(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + const char *pdev = nc->dev; + + if (nc->state != CONNECTED) + return not_connected(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &pdev, + DBUS_TYPE_INVALID); +} + +/* Connect and initiate BNEP session */ +static DBusMessage *connection_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + int err; + + if (nc->state != DISCONNECTED) + return already_connected(msg); + + nc->state = CONNECTING; + nc->msg = dbus_message_ref(msg); + + err = bt_l2cap_connect(&nc->src, &nc->dst, BNEP_PSM, BNEP_MTU, + connect_cb, nc); + if (err < 0) { + error("Connect failed. %s(%d)", strerror(errno), errno); + dbus_message_unref(nc->msg); + nc->msg = NULL; + nc->state = DISCONNECTED; + return connection_attempt_failed(msg, -err); + } + + return NULL; +} + +static DBusMessage *connection_cancel(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + + if (nc->state != CONNECTING) + return no_pending_connect(msg); + + close(nc->sk); + nc->state = DISCONNECTED; + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *connection_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + + if (nc->state != CONNECTED) + return not_connected(msg); + + bnep_if_down(nc->dev); + bnep_kill_connection(&nc->dst); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *is_connected(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + gboolean up = (nc->state == CONNECTED); + + return g_dbus_create_reply(msg, DBUS_TYPE_BOOLEAN, &up, + DBUS_TYPE_INVALID); +} + +static DBusMessage *get_info(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_conn *nc = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *uuid; + char raddr[18]; + const char *paddr = raddr; + + reply = dbus_message_new_method_return(msg); + if (reply == NULL) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + dbus_message_iter_append_dict_entry(&dict, "name", + DBUS_TYPE_STRING, &nc->name); + + uuid = bnep_uuid(nc->id); + dbus_message_iter_append_dict_entry(&dict, "uuid", + DBUS_TYPE_STRING, &uuid); + + ba2str(&nc->dst, raddr); + dbus_message_iter_append_dict_entry(&dict, "address", + DBUS_TYPE_STRING, &paddr); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static void connection_free(struct network_conn *nc) +{ + if (!nc) + return; + + if (nc->path) + g_free(nc->path); + + if (nc->state == CONNECTED) { + bnep_if_down(nc->dev); + bnep_kill_connection(&nc->dst); + } + + if (nc->name) + g_free(nc->name); + + if (nc->desc) + g_free(nc->desc); + + g_free(nc); + nc = NULL; +} + +static void connection_unregister(void *data) +{ + struct network_conn *nc = data; + + info("Unregistered connection path:%s", nc->path); + + connections = g_slist_remove(connections, nc); + connection_free(nc); +} + +static GDBusMethodTable connection_methods[] = { + { "GetAdapter", "", "s", get_adapter }, + { "GetAddress", "", "s", get_address }, + { "GetUUID", "", "s", get_uuid }, + { "GetName", "", "s", get_name }, + { "GetDescription", "", "s", get_description }, + { "GetInterface", "", "s", get_interface }, + { "Connect", "", "s", connection_connect, + G_DBUS_METHOD_FLAG_ASYNC }, + { "CancelConnect", "", "", connection_cancel }, + { "Disconnect", "", "", connection_disconnect }, + { "IsConnected", "", "b", is_connected }, + { "GetInfo", "", "a{sv}",get_info }, + { } +}; + +static GDBusSignalTable connection_signals[] = { + { "Connected", "" }, + { "Disconnected", "" }, + { } +}; + +int connection_register(const char *path, bdaddr_t *src, bdaddr_t *dst, + uint16_t id, const char *name, const char *desc) +{ + struct network_conn *nc; + bdaddr_t default_src; + int dev_id; + + if (!path) + return -EINVAL; + + bacpy(&default_src, BDADDR_ANY); + dev_id = hci_get_route(&default_src); + if (dev_id < 0 || hci_devba(dev_id, &default_src) < 0) + return -1; + + nc = g_new0(struct network_conn, 1); + + if (g_dbus_register_interface(connection, path, + NETWORK_CONNECTION_INTERFACE, + connection_methods, + connection_signals, NULL, + nc, connection_unregister) == FALSE) { + error("D-Bus failed to register %s interface", + NETWORK_CONNECTION_INTERFACE); + return -1; + } + + nc->path = g_strdup(path); + bacpy(&nc->store, src); + bacpy(&nc->src, &default_src); + bacpy(&nc->dst, dst); + nc->id = id; + nc->name = g_strdup(name); + nc->desc = g_strdup(desc); + memset(nc->dev, 0, 16); + strncpy(nc->dev, prefix, strlen(prefix)); + nc->state = DISCONNECTED; + + connections = g_slist_append(connections, nc); + + info("Registered connection path:%s", path); + + return 0; +} + +int connection_store(const char *path, gboolean default_path) +{ + struct network_conn *nc; + const char *role; + char key[32], *value; + char filename[PATH_MAX + 1]; + char src_addr[18], dst_addr[18]; + int len, err; + GSList *l; + + l = g_slist_find_custom(connections, path, find_connection); + if (!l) + return -ENOENT; + + nc = l->data; + if (!nc->name || !nc->desc) + return -EINVAL; + + /* FIXME: name and desc validation - remove ':' */ + + ba2str(&nc->dst, dst_addr); + role = bnep_name(nc->id); + snprintf(key, 32, "%s#%s", dst_addr, role); + + ba2str(&nc->store, src_addr); + create_name(filename, PATH_MAX, STORAGEDIR, src_addr, "network"); + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (default_path) + err = textfile_put(filename, "default", key); + else { + len = strlen(nc->name) + strlen(nc->desc) + 2; + value = g_malloc0(len); + snprintf(value, len, "%s:%s", nc->name, nc->desc); + err = textfile_put(filename, key, value); + g_free(value); + } + + return err; +} + +int connection_find_data(const char *path, const char *pattern) +{ + struct network_conn *nc; + char addr[18], key[32]; + const char *role; + GSList *l; + + l = g_slist_find_custom(connections, path, find_connection); + if (!l) + return -1; + + nc = l->data; + if (strcasecmp(pattern, nc->dev) == 0) + return 0; + + if (strcasecmp(pattern, nc->name) == 0) + return 0; + + ba2str(&nc->dst, addr); + + if (strcasecmp(pattern, addr) == 0) + return 0; + + role = bnep_name(nc->id); + snprintf(key, 32, "%s#%s", addr, role); + + if (strcasecmp(pattern, key) == 0) + return 0; + + return -1; +} + +gboolean connection_has_pending(const char *path) +{ + struct network_conn *nc; + GSList *l; + + l = g_slist_find_custom(connections, path, find_connection); + if (!l) + return FALSE; + + nc = l->data; + + return (nc->state == CONNECTING); +} + +int connection_remove_stored(const char *path) +{ + struct network_conn *nc; + const char *role; + char key[32]; + char filename[PATH_MAX + 1]; + char src_addr[18], dst_addr[18]; + int err; + GSList *l; + + l = g_slist_find_custom(connections, path, find_connection); + if (!l) + return -ENOENT; + + nc = l->data; + + ba2str(&nc->dst, dst_addr); + role = bnep_name(nc->id); + snprintf(key, 32, "%s#%s", dst_addr, role); + + ba2str(&nc->store, src_addr); + create_name(filename, PATH_MAX, STORAGEDIR, src_addr, "network"); + + err = textfile_del(filename, key); + + return err; +} + +gboolean connection_is_connected(const char *path) +{ + struct network_conn *nc; + GSList *l; + + l = g_slist_find_custom(connections, path, find_connection); + if (!l) + return FALSE; + + nc = l->data; + + return (nc->state == CONNECTED); +} + +int connection_init(DBusConnection *conn, const char *iface_prefix) +{ + connection = dbus_connection_ref(conn); + prefix = iface_prefix; + + return 0; +} + +void connection_exit() +{ + dbus_connection_unref(connection); + connection = NULL; + prefix = NULL; +} diff --git a/network/connection.h b/network/connection.h new file mode 100644 index 00000000..fd15e816 --- /dev/null +++ b/network/connection.h @@ -0,0 +1,34 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define NETWORK_CONNECTION_INTERFACE "org.bluez.network.Connection" + +int connection_init(DBusConnection *conn, const char *iface_prefix); +void connection_exit(); +int connection_register(const char *path, bdaddr_t *src, bdaddr_t *dst, + uint16_t id, const char *name, const char *desc); +int connection_store(const char *path, gboolean default_path); +int connection_remove_stored(const char *path); +int connection_find_data(const char *path, const char *pattern); +gboolean connection_has_pending(const char *path); +gboolean connection_is_connected(const char *path); diff --git a/network/main.c b/network/main.c new file mode 100644 index 00000000..f18223cc --- /dev/null +++ b/network/main.c @@ -0,0 +1,256 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/sdp.h> + +#include <gdbus.h> + +#include "plugin.h" +#include "device.h" +#include "logging.h" +#include "manager.h" + +#define IFACE_PREFIX "bnep%d" +#define GN_IFACE "pan0" +#define NAP_IFACE "pan1" + +#define PANU_UUID "00001115-0000-1000-8000-00805f9b34fb" +#define NAP_UUID "00001116-0000-1000-8000-00805f9b34fb" +#define GN_UUID "00001117-0000-1000-8000-00805f9b34fb" + +#define NETWORK_INTERFACE "org.bluez.Network" + +static DBusMessage *network_connect(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *target, *device = "bnep0"; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &target, + DBUS_TYPE_INVALID) == FALSE) + return NULL; + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &device, + DBUS_TYPE_INVALID); +} + +static DBusMessage *network_disconnect(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID) == FALSE) + return NULL; + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static GDBusMethodTable network_methods[] = { + { "Connect", "s", "s", network_connect }, + { "Disconnect", "", "", network_disconnect }, + { } +}; + +static GDBusSignalTable network_signals[] = { + { "Connected", "ss" }, + { "Disconnected", "s" }, + { } +}; + +static DBusConnection *conn; + +static int network_probe(struct btd_device *device) +{ + DBG("path %s", device->path); + + if (g_dbus_register_interface(conn, device->path, NETWORK_INTERFACE, + network_methods, network_signals, NULL, + device, NULL) == FALSE) + return -1; + + return 0; +} + +static void network_remove(struct btd_device *device) +{ + DBG("path %s", device->path); + + g_dbus_unregister_interface(conn, device->path, NETWORK_INTERFACE); +} + +static struct btd_device_driver network_driver = { + .name = "network", + .uuids = BTD_UUIDS(PANU_UUID, NAP_UUID, GN_UUID), + .probe = network_probe, + .remove = network_remove, +}; + +static struct network_conf conf = { + .connection_enabled = TRUE, + .server_enabled = TRUE, + .iface_prefix = NULL, + .panu_script = NULL, + .gn_script = NULL, + .nap_script = NULL, + .gn_iface = NULL, + .nap_iface = NULL, + .security = TRUE +}; + +static void read_config(const char *file) +{ + GKeyFile *keyfile; + GError *err = NULL; + char **disabled; + + keyfile = g_key_file_new(); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + goto done; + } + + disabled = g_key_file_get_string_list(keyfile, "General", + "Disable", NULL, &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } else { + int i; + for (i = 0; disabled[i] != NULL; i++) { + if (g_str_equal(disabled[i], "Connection")) + conf.connection_enabled = FALSE; + else if (g_str_equal(disabled[i], "Server")) + conf.server_enabled = FALSE; + } + g_strfreev(disabled); + } + + conf.security = !g_key_file_get_boolean(keyfile, "General", + "DisableSecurity", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } + + conf.panu_script = g_key_file_get_string(keyfile, "PANU Role", + "Script", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } + + conf.gn_script = g_key_file_get_string(keyfile, "GN Role", + "Script", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } + + conf.nap_script = g_key_file_get_string(keyfile, "NAP Role", + "Script", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } + + conf.iface_prefix = g_key_file_get_string(keyfile, "PANU Role", + "Interface", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } + + conf.gn_iface = g_key_file_get_string(keyfile, "GN Role", + "Interface", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } + + conf.nap_iface = g_key_file_get_string(keyfile, "NAP Role", + "Interface", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } + +done: + g_key_file_free(keyfile); + + if (!conf.iface_prefix) + conf.iface_prefix = g_strdup(IFACE_PREFIX); + if (!conf.gn_iface) + conf.gn_iface = g_strdup(GN_IFACE); + if (!conf.nap_iface) + conf.nap_iface = g_strdup(NAP_IFACE); + + debug("Config options: InterfacePrefix=%s, PANU_Script=%s, " + "GN_Script=%s, NAP_Script=%s, GN_Interface=%s, " + "NAP_Interface=%s, Security=%s", + conf.iface_prefix, conf.panu_script, conf.gn_script, + conf.nap_script, conf.gn_iface, conf.nap_iface, + conf.security ? "true" : "false"); +} + +static int network_init(void) +{ + conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (conn == NULL) + return -EIO; + + read_config(CONFIGDIR "/network.conf"); + + if (network_manager_init(conn, &conf) < 0) { + dbus_connection_unref(conn); + return -EIO; + } + + btd_register_device_driver(&network_driver); + + return 0; +} + +static void network_exit(void) +{ + btd_unregister_device_driver(&network_driver); + + network_manager_exit(); + + dbus_connection_unref(conn); +} + +BLUETOOTH_PLUGIN_DEFINE("network", network_init, network_exit) diff --git a/network/manager.c b/network/manager.c new file mode 100644 index 00000000..dd23b58d --- /dev/null +++ b/network/manager.c @@ -0,0 +1,855 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <ctype.h> +#include <dirent.h> + +#include <sys/stat.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/bnep.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <gdbus.h> + +#include "logging.h" +#include "textfile.h" +#include "glib-helper.h" + +#define NETWORK_MANAGER_INTERFACE "org.bluez.network.Manager" + +#include "error.h" +#include "bridge.h" +#include "manager.h" +#include "common.h" + +#define MAX_NAME_SIZE 256 + +struct pending_reply { + DBusConnection *conn; + DBusMessage *msg; + bdaddr_t src; /* Source address */ + bdaddr_t dst; /* Destination address */ + char *addr; /* Destination address */ + char *path; /* D-Bus object path */ + char *adapter_path; /* Default adapter path */ + uint16_t id; /* Role */ +}; + +static struct network_conf *conf = NULL;/* Network service configuration */ +static GSList *server_paths = NULL; /* Network registered servers paths */ +static GSList *connection_paths = NULL; /* Network registered connections paths */ +static int default_index = -1; /* Network default connection path index */ +static int net_uid = 0; /* Network objects identifier */ + +static DBusConnection *connection = NULL; + +static void pending_reply_free(struct pending_reply *pr) +{ + if (pr->addr) + g_free(pr->addr); + if (pr->path) + g_free(pr->path); + if (pr->adapter_path) + g_free(pr->adapter_path); + if (pr->msg) + dbus_message_unref(pr->msg); + if (pr->conn) + dbus_connection_unref(pr->conn); +} + +static inline DBusMessage *does_not_exist(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", + "No such connection"); +} + +static inline DBusMessage *already_exists(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", + "Connection already exists"); +} + +static inline DBusMessage *not_supported(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", + "Not supported"); +} + +static inline DBusMessage *connection_is_busy(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Connection is Busy"); +} + +static inline DBusMessage *adapter_not_available(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Adapter not available"); +} + +static void create_path(DBusConnection *conn, DBusMessage *msg, + const char *path, const char *sname) +{ + /* emit signal when it is a new path */ + if (sname) { + g_dbus_emit_signal(conn, NETWORK_PATH, + NETWORK_MANAGER_INTERFACE, + sname, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + } + + g_dbus_send_reply(conn, msg, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); +} + +static DBusMessage *list_paths(DBusConnection *conn, DBusMessage *msg, + GSList *list) +{ + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter array_iter; + + reply = dbus_message_new_method_return(msg); + if (reply == NULL) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array_iter); + + for (; list; list = list->next) { + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_STRING, + &list->data); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static const char *last_connection_used(DBusConnection *conn) +{ + const char *path = NULL; + GSList *l; + int i; + + for (i = g_slist_length (connection_paths) -1; i > -1; i--) { + path = g_slist_nth_data (connection_paths, i); + if (connection_is_connected(path)) + break; + } + + /* No connection connected fallback to last connection */ + if (i == -1) { + l = g_slist_last(connection_paths); + path = l->data; + } + + return path; +} + +static DBusMessage *remove_path(DBusConnection *conn, + DBusMessage *msg, GSList **list, + const char *sname) +{ + const char *path; + GSList *l; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID) == FALSE) + return NULL; + + l = g_slist_find_custom(*list, path, (GCompareFunc) strcmp); + if (!l) + return does_not_exist(msg); + + /* Remove references from the storage */ + if (*list == connection_paths) { + if (connection_has_pending(path)) + return connection_is_busy(msg); + + connection_remove_stored(path); + /* Reset default connection */ + if (l == g_slist_nth(*list, default_index)) { + const char *dpath; + + dpath = last_connection_used(conn); + connection_store(dpath, TRUE); + } + } + + g_free(l->data); + *list = g_slist_remove(*list, l->data); + + g_dbus_emit_signal(conn, NETWORK_PATH, + NETWORK_MANAGER_INTERFACE, + sname, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + + g_dbus_unregister_interface(conn, path, NETWORK_CONNECTION_INTERFACE); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void records_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct pending_reply *pr = data; + int len; + sdp_data_t *d; + sdp_record_t *rec = NULL; + char name[MAX_NAME_SIZE], *desc = NULL; + + if (err < 0) { + error_connection_attempt_failed(pr->conn, pr->msg, -err); + goto fail; + } + + if (!recs || !recs->data) { + error_not_supported(pr->conn, pr->msg); + error("Invalid PAN service record"); + goto fail; + } + + rec = recs->data; + + /* Concat remote name and service name */ + memset(name, 0, MAX_NAME_SIZE); + if (read_remote_name(&pr->src, &pr->dst, name, MAX_NAME_SIZE) < 0) + len = 0; + else + len = strlen(name); + + d = sdp_data_get(rec, SDP_ATTR_SVCNAME_PRIMARY); + if (d) { + snprintf(name + len, MAX_NAME_SIZE - len, + len ? " (%.*s)" : "%.*s", d->unitSize, d->val.str); + } + + /* Extract service description from record */ + d = sdp_data_get(rec, SDP_ATTR_SVCDESC_PRIMARY); + if (d) { + desc = g_new0(char, d->unitSize); + snprintf(desc, d->unitSize, "%.*s", + d->unitSize, d->val.str); + } + + sdp_list_free(recs, (sdp_free_func_t) sdp_record_free); + + if (connection_register(pr->path, &pr->src, &pr->dst, pr->id, name, + desc) < 0) { + error_failed(pr->conn, pr->msg, "D-Bus path registration failed"); + goto fail; + } + + connection_store(pr->path, FALSE); + connection_paths = g_slist_append(connection_paths, g_strdup(pr->path)); + + create_path(pr->conn, pr->msg, pr->path, "ConnectionCreated"); + +fail: + g_free(desc); + pending_reply_free(pr); +} + +static DBusMessage *list_servers(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return list_paths(conn, msg, server_paths); +} + +static DBusMessage *find_server(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *pattern; + const char *path; + GSList *list; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID) == FALSE) + return NULL; + + for (list = server_paths; list; list = list->next) { + path = (const char *) list->data; + if (server_find_data(path, pattern) == 0) + break; + } + + if (list == NULL) + return does_not_exist(msg); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); +} + +static DBusMessage *list_connections(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return list_paths(conn, msg, connection_paths); +} + +static GSList *find_connection_pattern(DBusConnection *conn, + const char *pattern) +{ + const char *path; + GSList *list; + + if (pattern == NULL) + return NULL; + + for (list = connection_paths; list; list = list->next) { + path = (const char *) list->data; + if (connection_find_data(path, pattern) == 0) + break; + } + + return list; +} + +static DBusMessage *find_connection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *pattern; + const char *path; + GSList *list; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID) == FALSE) + return NULL; + + list = find_connection_pattern(conn, pattern); + if (list == NULL) + return does_not_exist(msg); + + path = list->data; + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); +} + +static DBusMessage *create_connection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct pending_reply *pr; + const char *addr; + const char *str; + bdaddr_t src; + uint16_t id; + int dev_id, err; + char key[32]; + GSList *l; + uuid_t uuid; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr, + DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID) == FALSE) + return NULL; + + id = bnep_service_id(str); + if (id != BNEP_SVC_GN && id != BNEP_SVC_NAP && id != BNEP_SVC_PANU) + return not_supported(msg); + + snprintf(key, 32, "%s#%s", addr, bnep_name(id)); + + /* Checks if the connection was already been made */ + for (l = connection_paths; l; l = l->next) { + if (connection_find_data(l->data, key) == 0) + return already_exists(msg); + } + + bacpy(&src, BDADDR_ANY); + dev_id = hci_get_route(&src); + if (dev_id < 0 || hci_devba(dev_id, &src) < 0) + return adapter_not_available(msg); + + pr = g_new0(struct pending_reply, 1); + + /* FIXME just to maintain compatibility */ + pr->adapter_path = g_strdup_printf("/org/bluez/hci%d", dev_id); + if (!pr->adapter_path) { + pending_reply_free (pr); + return adapter_not_available(msg); + } + + pr->conn = dbus_connection_ref(conn); + pr->msg = dbus_message_ref(msg); + bacpy(&pr->src, &src); + str2ba(addr, &pr->dst); + pr->addr = g_strdup(addr); + pr->id = id; + pr->path = g_new0(char, MAX_PATH_LENGTH); + snprintf(pr->path, MAX_PATH_LENGTH, + NETWORK_PATH "/connection%d", net_uid++); + + sdp_uuid16_create(&uuid, pr->id); + err = bt_search_service(&pr->src, &pr->dst, &uuid, records_cb, pr, + NULL); + if (err < 0) { + pending_reply_free(pr); + return not_supported(msg); + } + + return NULL; +} + +static DBusMessage *remove_connection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return remove_path(conn, msg, &connection_paths, "ConnectionRemoved"); +} + +static DBusMessage *last_connection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path; + + if (connection_paths == NULL || + g_slist_length(connection_paths) == 0) + return does_not_exist(msg); + + path = last_connection_used(conn); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); +} + +static DBusMessage *default_connection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path; + + if (connection_paths == NULL || + g_slist_length (connection_paths) == 0) + return does_not_exist(msg); + + path = g_slist_nth_data (connection_paths, default_index); + + if (path == NULL) { + path = last_connection_used(conn); + connection_store(path, TRUE); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); +} + +static DBusMessage *change_default_connection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path; + const char *pattern; + GSList *list; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID) == FALSE) + return NULL; + + if (connection_paths == NULL || + g_slist_length(connection_paths) == 0) + return does_not_exist(msg); + + list = g_slist_find_custom(connection_paths, pattern, + (GCompareFunc) strcmp); + + /* Find object path via pattern */ + if (list == NULL) { + list = find_connection_pattern(conn, pattern); + if (list == NULL) + return does_not_exist(msg); + + path = list->data; + } else + path = list->data; + + default_index = g_slist_position(connection_paths, list); + connection_store(path, TRUE); + + g_dbus_emit_signal(connection, NETWORK_PATH, + NETWORK_MANAGER_INTERFACE, + "DefaultConnectionChanged", + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + + return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); +} + +static void manager_unregister(void *data) +{ + info("Unregistered manager path"); + + if (server_paths) { + g_slist_foreach(server_paths, (GFunc) g_free, NULL); + g_slist_free(server_paths); + server_paths = NULL; + } + + if (connection_paths) { + g_slist_foreach(connection_paths, (GFunc) g_free, NULL); + g_slist_free(connection_paths); + connection_paths = NULL; + } + + bnep_kill_all_connections(); +} + +static void parse_stored_connection(char *key, char *value, void *data) +{ + bdaddr_t dst, *src = data; + char path[MAX_PATH_LENGTH]; + char addr[18]; + const char *ptr; + char *name; + int len, id; + + /* Format: XX:XX:XX:XX:XX:XX#{NAP, GN} name:description */ + + /* Parsing the key: address#role */ + ptr = strchr(key, '#'); + + /* Empty address or invalid len */ + if (!ptr || ((ptr - key) != 17)) + return; + + memset(addr, 0, 18); + strncpy(addr, key, 17); + str2ba(addr, &dst); + + /* Empty role */ + if (++ptr == NULL) + return; + + if (strcasecmp("nap", ptr) == 0) + id = BNEP_SVC_NAP; + else if (strcasecmp("gn", ptr) == 0) + id = BNEP_SVC_GN; + else if (strcasecmp("panu", ptr) == 0) + id = BNEP_SVC_PANU; + else + return; + + snprintf(path, MAX_PATH_LENGTH, + NETWORK_PATH "/connection%d", net_uid++); + + /* Parsing the value: name and description */ + ptr = strchr(value, ':'); + + /* Empty name */ + if (!ptr) + return; + + len = ptr-value; + name = g_malloc0(len + 1); + strncpy(name, value, len); + + /* Empty description */ + if (++ptr == NULL) { + g_free(name); + return; + } + + if (connection_register(path, src, &dst, id, name, ptr) == 0) { + char *rpath = g_strdup(path); + connection_paths = g_slist_append(connection_paths, rpath); + } + + g_free(name); +} + +static void register_connections_stored(const char *adapter) +{ + char filename[PATH_MAX + 1]; + char *pattern; + struct stat st; + GSList *list; + bdaddr_t src; + bdaddr_t default_src; + int dev_id; + + create_name(filename, PATH_MAX, STORAGEDIR, adapter, "network"); + + str2ba(adapter, &src); + + if (stat(filename, &st) < 0) + return; + + if (!(st.st_mode & __S_IFREG)) + return; + + textfile_foreach(filename, parse_stored_connection, &src); + + /* Check default connection for current default adapter */ + bacpy(&default_src, BDADDR_ANY); + dev_id = hci_get_route(&default_src); + if (dev_id < 0 || hci_devba(dev_id, &default_src) < 0) + return; + + if (bacmp(&default_src, &src) != 0) + return; + + pattern = textfile_get(filename, "default"); + if (!pattern) + return; + + list = find_connection_pattern(connection, pattern); + if (!list) + return; + default_index = g_slist_position(connection_paths, list); +} + +static void register_server(uint16_t id) +{ + char path[MAX_PATH_LENGTH]; + bdaddr_t src; + int dev_id; + + if (!conf->server_enabled) + return; + + snprintf(path, MAX_PATH_LENGTH, NETWORK_PATH "/%s", bnep_name(id)); + + if (g_slist_find_custom(server_paths, path, + (GCompareFunc) strcmp)) + return; + + bacpy(&src, BDADDR_ANY); + dev_id = hci_get_route(&src); + if (dev_id < 0 || hci_devba(dev_id, &src)) + return; + + if (server_register(path, &src, id) < 0) + return; + + server_store(path); + + server_paths = g_slist_append(server_paths, g_strdup(path)); +} + +static void register_servers_stored(const char *adapter, const char *profile) +{ + char filename[PATH_MAX + 1]; + char path[MAX_PATH_LENGTH]; + uint16_t id; + struct stat s; + bdaddr_t src; + + if (strcmp(profile, "nap") == 0) + id = BNEP_SVC_NAP; + else if (strcmp(profile, "gn") == 0) + id = BNEP_SVC_GN; + else + id = BNEP_SVC_PANU; + + create_name(filename, PATH_MAX, STORAGEDIR, adapter, profile); + + str2ba(adapter, &src); + + if (stat (filename, &s) == 0 && (s.st_mode & __S_IFREG)) { + snprintf(path, MAX_PATH_LENGTH, NETWORK_PATH "/%s", profile); + if (server_register_from_file(path, &src, id, filename) == 0) { + server_paths = g_slist_append(server_paths, + g_strdup(path)); + } + } +} + +static void register_stored(void) +{ + char dirname[PATH_MAX + 1]; + struct dirent *de; + DIR *dir; + + snprintf(dirname, PATH_MAX, "%s", STORAGEDIR); + + dir = opendir(dirname); + if (!dir) + return; + + while ((de = readdir(dir)) != NULL) { + if (!isdigit(de->d_name[0])) + continue; + + /* Connection objects */ + if (conf->connection_enabled) + register_connections_stored(de->d_name); + + /* Server objects */ + if (conf->server_enabled) { + /* NAP objects */ + register_servers_stored(de->d_name, "nap"); + + /* GN objects */ + register_servers_stored(de->d_name, "gn"); + + /* PANU objects */ + register_servers_stored(de->d_name, "panu"); + } + } + + closedir(dir); +} + +static GDBusMethodTable connection_methods[] = { + { "ListConnections", "", "as", list_connections }, + { "FindConnection", "s", "s", find_connection }, + { "CreateConnection", "ss", "s", create_connection, + G_DBUS_METHOD_FLAG_ASYNC }, + { "RemoveConnection", "s", "", remove_connection }, + { "LastConnection", "", "s", last_connection }, + { "DefaultConnection", "", "s", default_connection }, + { "ChangeDefaultConnection", "s", "s", change_default_connection }, + { } +}; + +static GDBusSignalTable connection_signals[] = { + { "ConnectionCreated", "s" }, + { "ConnectionRemoved", "s" }, + { "DefaultConnectionChanged", "s" }, + { } +}; + +static GDBusMethodTable server_methods[] = { + { "ListServers", "", "as", list_servers }, + { "FindServer", "s", "s", find_server }, + { } +}; + +static GDBusMethodTable manager_methods[] = { + { "ListServers", "", "as", list_servers }, + { "FindServer", "s", "s", find_server }, + { "ListConnections", "", "as", list_connections }, + { "FindConnection", "s", "s", find_connection }, + { "CreateConnection", "ss", "s", create_connection, + G_DBUS_METHOD_FLAG_ASYNC }, + { "RemoveConnection", "s", "", remove_connection }, + { "LastConnection", "", "s", last_connection }, + { "DefaultConnection", "", "s", default_connection }, + { "ChangeDefaultConnection", "s", "s", change_default_connection }, + { } +}; + +int network_manager_init(DBusConnection *conn, struct network_conf *service_conf) +{ + GDBusMethodTable *methods = NULL; + GDBusSignalTable *signals = NULL; + + conf = service_conf; + + if (conf->server_enabled && conf->connection_enabled) { + methods = manager_methods; + signals = connection_signals; + } else if (conf->connection_enabled) { + methods = connection_methods; + signals = connection_signals; + } else if (conf->server_enabled) + methods = server_methods; + else { + error ("All interfaces were disabled"); + return -1; + } + + if (bnep_init(conf->panu_script, conf->gn_script, conf->nap_script)) { + error("Can't init bnep module"); + return -1; + } + + /* + * There is one socket to handle the incomming connections. NAP, + * GN and PANU servers share the same PSM. The initial BNEP message + * (setup connection request) contains the destination service + * field that defines which service the source is connecting to. + */ + if (conf->server_enabled) { + if (bridge_init(conf->gn_iface, conf->nap_iface) < 0) { + error("Can't init bridge module"); + return -1; + } + + if (server_init(conn, conf->iface_prefix, conf->security) < 0) + return -1; + } + + if (conf->connection_enabled) { + if (connection_init(conn, conf->iface_prefix) < 0) + return -1; + } + + if (g_dbus_register_interface(conn, NETWORK_PATH, + NETWORK_MANAGER_INTERFACE, + methods, signals, NULL, + NULL, manager_unregister) == FALSE) { + error("Failed to register %s interface to %s", + NETWORK_MANAGER_INTERFACE, NETWORK_PATH); + return -1; + } + + connection = dbus_connection_ref(conn); + + info("Registered manager path:%s", NETWORK_PATH); + + register_stored(); + + /* Register PANU, GN and NAP servers if they don't exist */ + register_server(BNEP_SVC_PANU); + register_server(BNEP_SVC_GN); + register_server(BNEP_SVC_NAP); + + return 0; +} + +void network_manager_exit(void) +{ + if (conf->server_enabled) + server_exit(); + + if (conf->connection_enabled) + connection_exit(); + + g_dbus_unregister_interface(connection, NETWORK_PATH, + NETWORK_MANAGER_INTERFACE); + + dbus_connection_unref(connection); + connection = NULL; + + bnep_cleanup(); + bridge_cleanup(); +} + +static inline int create_filename(char *buf, size_t size, + bdaddr_t *bdaddr, const char *name) +{ + char addr[18]; + + ba2str(bdaddr, addr); + + return create_name(buf, size, STORAGEDIR, addr, name); +} diff --git a/network/manager.h b/network/manager.h new file mode 100644 index 00000000..9b16c2a3 --- /dev/null +++ b/network/manager.h @@ -0,0 +1,43 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "connection.h" +#include "server.h" + +#define MAX_PATH_LENGTH 64 /* D-Bus path */ +#define NETWORK_PATH "/org/bluez/network" + +struct network_conf { + gboolean connection_enabled; + gboolean server_enabled; + gboolean security; + char *iface_prefix; + char *panu_script; + char *gn_script; + char *nap_script; + char *gn_iface; + char *nap_iface; +}; + +int network_manager_init(DBusConnection *conn, struct network_conf *service_conf); +void network_manager_exit(void); diff --git a/network/network-api.txt b/network/network-api.txt new file mode 100644 index 00000000..2d46abfd --- /dev/null +++ b/network/network-api.txt @@ -0,0 +1,199 @@ +Bluetooth network service API description +***************************************** + +Copyright (C) 2006-2007 Marcel Holtmann <marcel@holtmann.org> + + +Network Manager hierarchy +========================= + +Interface org.bluez.network.Manager +Object path /org/bluez/network + +Methods array{string} ListServers() + + Returns an array of available network devices paths. + Currently only NAP and GN are supported. + + string FindServer(string pattern) + + Returns server path. + + Possible errors: org.bluez.Error.DoesNotExist + org.bluez.Error.Failed + + string CreateConnection(string address, string uuid) + + Creates a network connection object(NAP or GN). + + Possible errors: org.bluez.Error.AlreadyExists + org.bluez.Error.NotSupported + org.bluez.Error.ConnectionAttemptFailed + org.bluez.Error.Failed + + void RemoveConnection(string path) + + Removes a network connection object for a given path. + + Possible errors: org.bluez.Error.DoesNotExist + org.bluez.Error.Failed + + array{string} ListConnections() + + Returns an array of available network connections paths. + + string FindConnection(string pattern) + + Returns connection path. + + Possible errors: org.bluez.Error.DoesNotExist + org.bluez.Error.Failed + + string LastConnection() + + Returns last connected connection path, if none is connected + fallback to last created connection. + + Possible errors: org.bluez.Error.DoesNotExist + + string DefaultConnection() + + Returns default connection path. + + Possible errors: org.bluez.Error.DoesNotExist + + string ChangeDefaultConnection(string pattern) + + Changes default connection path. + + Possible errors: org.bluez.Error.DoesNotExist + +Signals void ConnectionCreated(string path) + + void ConnectionRemoved(string path) + + void DefaultConnectionChanged(string path) + + +Network Server hierarchy (experimental) +======================================= + +Interface org.bluez.network.Server +Object path /org/bluez/network/{gn, nap, panu} + +Methods string GetUUID() + + Returns the UUID-128 string representation of + the server. + + void Enable() + + Enable server and updates service record. + + Possible errors: org.bluez.Error.AlreadyExists + org.bluez.Error.Failed + + void Disable() + + Disable server and remove service record. + + Possible errors: org.bluez.Error.Failed + + bool IsEnabled() + + Returns the server status. + + void SetName(string name) + + Sets the name attribute. + + string GetName() + + Returns the service name. + + void SetAddressRange(string start, string end) + + TBD + + void SetRouting(string interface) + + TBD + + dict GetInfo() + + Returns the server properties. + +Signals void Enabled() + + void Disabled() + + +Network Connection hierarchy (experimental) +=========================================== + +Interface org.bluez.network.Connection +Object path /org/bluez/network/connection* + +Methods string GetAdapter() + + Returns the Bluetooth address of the adapter. + + string GetAddress() + + Returns the Bluetooth address of the ending point. + + string GetUUID() + + Returns the uuid 128 string representation of + the connected service. + + string GetName() + + Returns the string representation of connected host. + + Possible errors: org.bluez.Error.Failed + + string GetDescription() + + Returns the string description of connected host. + + Possible errors: org.bluez.Error.Failed + + string GetInterface() + + Returns the string network interface. + + Possible errors: org.bluez.Error.Failed + + string Connect() + + Connects to host and return the network interface + created. + + Possible errors: org.bluez.Error.ConnectionAttemptFailed + org.bluez.Error.Failed + + void CancelConnect() + + Abort connection attempt in case of errors or + timeouts in the client. + + Possible errors: org.bluez.Error.Failed + + void Disconnect() + + Disconnects to host. + + Possible errors: org.bluez.Error.Failed + + bool IsConnected() + + Returns the connection status. + + dict GetInfo() + + Returns the connection properties. + +Signals void Connected() + + void Disconnected() diff --git a/network/network.conf b/network/network.conf new file mode 100644 index 00000000..8677bd7b --- /dev/null +++ b/network/network.conf @@ -0,0 +1,37 @@ +# Configuration file for the network service + +# This section contains options which are not specific to any +# particular interface +[General] + +# If we want to disable support for specific services +# Defaults to supporting all implemented services +#Disable=Connection,Server + +# Disable link encryption: default=false +#DisableSecurity=true + +[PANU Role] + +# Network interface name for PANU for connections. default:bnep%d +# (up to 16 characters) +#Interface= + +# PAN user connection interface up script. default:none +Script=avahi-autoipd + +[GN Role] + +# Network Interface name for Group Network server. default:pan0 +#Interface= + +# Group Network connection interface up script. default:none +Script=avahi-autoipd + +[NAP Role] + +# Network Interface name for Network Access Point server. default:pan1 +#Interface= + +# Network Access Point connection interface up script. default:none +Script=dhclient diff --git a/network/server.c b/network/server.c new file mode 100644 index 00000000..e94964ae --- /dev/null +++ b/network/server.c @@ -0,0 +1,1097 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <net/if.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/bnep.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <netinet/in.h> + +#include <glib.h> +#include <gdbus.h> + +#include "../hcid/dbus-common.h" + +#include "logging.h" +#include "error.h" +#include "textfile.h" +#include "dbus-service.h" +#include "sdpd.h" +#include "glib-helper.h" + +#define NETWORK_SERVER_INTERFACE "org.bluez.network.Server" +#define SETUP_TIMEOUT 1000 +#define MAX_SETUP_ATTEMPTS 3 + +#include "bridge.h" +#include "common.h" +#include "manager.h" + +/* Pending Authorization */ +struct setup_session { + char *address; /* Remote Bluetooth Address */ + uint16_t dst_role; /* Destination role */ + uint16_t src_role; /* Source role */ + int nsk; /* L2CAP socket */ + guint watch; /* BNEP socket watch */ +}; + +struct timeout { + guint id; /* Timeout id */ + guint watch; /* BNEP socket watch */ +}; + +/* Main server structure */ +struct network_server { + bdaddr_t src; /* Bluetooth Local Address */ + char *iface; /* Routing interface */ + char *name; /* Server service name */ + char *range; /* IP Address range */ + char *path; /* D-Bus path */ + gboolean enable; /* Enable flag */ + uint32_t record_id; /* Service record id */ + uint16_t id; /* Service class identifier */ + GSList *clients; /* Active connections */ +}; + +static GIOChannel *bnep_io = NULL; +static DBusConnection *connection = NULL; +static struct setup_session *setup = NULL; +static GSList *servers = NULL; +static const char *prefix = NULL; +static gboolean security = TRUE; + +gint find_server(gconstpointer a, gconstpointer b) +{ + const struct network_server *ns = a; + const char *path = b; + + return strcmp(ns->path, path); +} + +static struct setup_session *setup_session_new(gchar *address, + uint16_t dst_role, uint16_t src_role, int nsk, guint watch) +{ + struct setup_session *setup; + + setup = g_new0(struct setup_session, 1); + setup->address = g_strdup(address); + setup->dst_role = dst_role; + setup->src_role = src_role; + setup->nsk = nsk; + setup->watch = watch; + + return setup; +} + +static void setup_session_free(struct setup_session *setup) +{ + g_source_remove(setup->watch); + g_free(setup->address); + g_free(setup); +} + +static struct network_server *server_find(bdaddr_t *src, uint16_t role) +{ + struct network_server *ns; + GSList *l; + + for (l = servers; l; l = l->next) { + ns = l->data; + if (bacmp(&ns->src, src) != 0) + continue; + if (ns->id == role) + return ns; + } + + return NULL; +} + +static int store_property(bdaddr_t *src, uint16_t id, + const char *key, const char *value) +{ + char filename[PATH_MAX + 1]; + char addr[18]; + + ba2str(src, addr); + if (id == BNEP_SVC_NAP) + create_name(filename, PATH_MAX, STORAGEDIR, addr, "nap"); + else if (id == BNEP_SVC_GN) + create_name(filename, PATH_MAX, STORAGEDIR, addr, "gn"); + else if (id == BNEP_SVC_PANU) + create_name(filename, PATH_MAX, STORAGEDIR, addr, "panu"); + + return textfile_put(filename, key, value); +} + +static void add_lang_attr(sdp_record_t *r) +{ + sdp_lang_attr_t base_lang; + sdp_list_t *langs = 0; + + /* UTF-8 MIBenum (http://www.iana.org/assignments/character-sets) */ + base_lang.code_ISO639 = (0x65 << 8) | 0x6e; + base_lang.encoding = 106; + base_lang.base_offset = SDP_PRIMARY_LANG_BASE; + langs = sdp_list_append(0, &base_lang); + sdp_set_lang_attr(r, langs); + sdp_list_free(langs, 0); +} + +static sdp_record_t *server_record_new(const char *name, uint16_t id) +{ + sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; + uuid_t root_uuid, pan, l2cap, bnep; + sdp_profile_desc_t profile[1]; + sdp_list_t *proto[2]; + sdp_data_t *v, *p; + uint16_t psm = BNEP_PSM, version = 0x0100; + uint16_t security_desc = (security ? 0x0001 : 0x0000); + uint16_t net_access_type = 0xfffe; + uint32_t max_net_access_rate = 0; + const char *desc = "BlueZ PAN service"; + sdp_record_t *record; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + record->attrlist = NULL; + record->pattern = NULL; + + switch (id) { + case BNEP_SVC_NAP: + sdp_uuid16_create(&pan, NAP_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, name, NULL, desc); + + sdp_attr_add_new(record, SDP_ATTR_NET_ACCESS_TYPE, + SDP_UINT16, &net_access_type); + sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE, + SDP_UINT32, &max_net_access_rate); + break; + case BNEP_SVC_GN: + sdp_uuid16_create(&pan, GN_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, name, NULL, desc); + break; + case BNEP_SVC_PANU: + sdp_uuid16_create(&pan, PANU_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, name, NULL, desc); + break; + default: + return NULL; + } + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + p = sdp_data_alloc(SDP_UINT16, &psm); + proto[0] = sdp_list_append(proto[0], p); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&bnep, BNEP_UUID); + proto[1] = sdp_list_append(NULL, &bnep); + v = sdp_data_alloc(SDP_UINT16, &version); + proto[1] = sdp_list_append(proto[1], v); + + /* Supported protocols */ + { + uint16_t ptype[] = { + 0x0800, /* IPv4 */ + 0x0806, /* ARP */ + }; + sdp_data_t *head, *pseq; + int p; + + for (p = 0, head = NULL; p < 2; p++) { + sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]); + if (head) + sdp_seq_append(head, data); + else + head = data; + } + pseq = sdp_data_alloc(SDP_SEQ16, head); + proto[1] = sdp_list_append(proto[1], pseq); + } + + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + add_lang_attr(record); + + sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC, + SDP_UINT16, &security_desc); + + sdp_data_free(p); + sdp_data_free(v); + sdp_list_free(apseq, NULL); + sdp_list_free(root, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(svclass, NULL); + sdp_list_free(pfseq, NULL); + + return record; +} + +static ssize_t send_bnep_ctrl_rsp(int sk, uint16_t val) +{ + struct bnep_control_rsp rsp; + + rsp.type = BNEP_CONTROL; + rsp.ctrl = BNEP_SETUP_CONN_RSP; + rsp.resp = htons(val); + + return send(sk, &rsp, sizeof(rsp), 0); +} + +static int server_connadd(struct network_server *ns, int nsk, + const gchar *address, uint16_t dst_role) +{ + char devname[16]; + const char *bridge; + int err; + + /* Server can be disabled in the meantime */ + if (ns->enable == FALSE) + return -EPERM; + + memset(devname, 0, 16); + strncpy(devname, prefix, strlen(prefix)); + + err = bnep_connadd(nsk, dst_role, devname); + if (err < 0) + return err; + + info("Added new connection: %s", devname); + + bridge = bridge_get_name(ns->id); + if (bridge) { + if (bridge_add_interface(ns->id, devname) < 0) { + error("Can't add %s to the bridge %s: %s(%d)", + devname, bridge, strerror(errno), + errno); + return -EPERM; + } + + bnep_if_up(devname, 0); + } else + bnep_if_up(devname, ns->id); + + ns->clients = g_slist_append(ns->clients, g_strdup(address)); + + return 0; +} + +static void req_auth_cb(DBusError *derr, void *user_data) +{ + struct network_server *ns = user_data; + uint16_t val; + + if (!setup) { + info("Authorization cancelled: Client exited"); + return; + } + + if (derr) { + error("Access denied: %s", derr->message); + if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY)) { + bdaddr_t dst; + str2ba(setup->address, &dst); + service_cancel_auth(&ns->src, &dst); + } + val = BNEP_CONN_NOT_ALLOWED; + goto done; + } + + if (server_connadd(ns, setup->nsk, + setup->address, setup->dst_role) < 0) + val = BNEP_CONN_NOT_ALLOWED; + else + val = BNEP_SUCCESS; + +done: + send_bnep_ctrl_rsp(setup->nsk, val); + setup_session_free(setup); + setup = NULL; +} + +static int authorize_connection(struct network_server *ns, const char *address) +{ + const char *uuid; + bdaddr_t dst; + int ret_val; + + uuid = bnep_uuid(ns->id); + str2ba(address, &dst); + + ret_val = service_req_auth(&ns->src, &dst, uuid, req_auth_cb, ns); + + return ret_val; +} + +static uint16_t inline bnep_setup_chk(uint16_t dst_role, uint16_t src_role) +{ + /* Allowed PAN Profile scenarios */ + switch (dst_role) { + case BNEP_SVC_NAP: + case BNEP_SVC_GN: + if (src_role == BNEP_SVC_PANU) + return 0; + return BNEP_CONN_INVALID_SRC; + case BNEP_SVC_PANU: + if (src_role == BNEP_SVC_PANU || + src_role == BNEP_SVC_GN || + src_role == BNEP_SVC_NAP) + return 0; + + return BNEP_CONN_INVALID_SRC; + } + + return BNEP_CONN_INVALID_DST; +} + +static uint16_t bnep_setup_decode(struct bnep_setup_conn_req *req, + uint16_t *dst_role, uint16_t *src_role) +{ + uint8_t *dest, *source; + + dest = req->service; + source = req->service + req->uuid_size; + + switch (req->uuid_size) { + case 2: /* UUID16 */ + *dst_role = ntohs(bt_get_unaligned((uint16_t *) dest)); + *src_role = ntohs(bt_get_unaligned((uint16_t *) source)); + break; + case 4: /* UUID32 */ + case 16: /* UUID128 */ + *dst_role = ntohl(bt_get_unaligned((uint32_t *) dest)); + *src_role = ntohl(bt_get_unaligned((uint32_t *) source)); + break; + default: + return BNEP_CONN_INVALID_SVC; + } + + return 0; +} + +static gboolean bnep_setup(GIOChannel *chan, + GIOCondition cond, gpointer user_data) +{ + struct timeout *to = user_data; + struct network_server *ns; + uint8_t packet[BNEP_MTU]; + struct bnep_setup_conn_req *req = (void *) packet; + struct sockaddr_l2 sa; + socklen_t size; + char address[18]; + uint16_t rsp, src_role, dst_role; + int n, sk; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + error("Hangup or error on BNEP socket"); + return FALSE; + } + + sk = g_io_channel_unix_get_fd(chan); + + /* Reading BNEP_SETUP_CONNECTION_REQUEST_MSG */ + n = read(sk, packet, sizeof(packet)); + if (n < 0) { + error("read(): %s(%d)", strerror(errno), errno); + return FALSE; + } + + if (req->type != BNEP_CONTROL || req->ctrl != BNEP_SETUP_CONN_REQ) + return FALSE; + + rsp = bnep_setup_decode(req, &dst_role, &src_role); + if (rsp) + goto reply; + + rsp = bnep_setup_chk(dst_role, src_role); + if (rsp) + goto reply; + + size = sizeof(sa); + if (getsockname(sk, (struct sockaddr *) &sa, &size) < 0) { + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + ba2str(&sa.l2_bdaddr, address); + ns = server_find(&sa.l2_bdaddr, dst_role); + if (!ns || ns->enable == FALSE) { + error("Server unavailable: %s (0x%x)", address, dst_role); + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + if (getpeername(sk, (struct sockaddr *) &sa, &size) < 0) { + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + ba2str(&sa.l2_bdaddr, address); + + if (setup) { + error("Connection rejected: Pending authorization"); + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + setup = setup_session_new(address, dst_role, src_role, sk, to->watch); + + /* Wait authorization before reply success */ + if (authorize_connection(ns, address) < 0) { + setup_session_free(setup); + setup = NULL; + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + g_source_remove(to->id); + to->id = 0; + + return TRUE; + +reply: + send_bnep_ctrl_rsp(sk, rsp); + + return FALSE; +} + +static void setup_destroy(void *user_data) +{ + struct timeout *to = user_data; + + if (to->id) + g_source_remove(to->id); + + g_free(to); +} + +static gboolean timeout_cb(void *user_data) +{ + struct timeout *to = user_data; + + to->id = 0; + g_source_remove(to->watch); + + return FALSE; +} + +static void connect_event(GIOChannel *chan, int err, const bdaddr_t *src, + const bdaddr_t *dst, gpointer user_data) +{ + struct timeout *to; + + if (err < 0) { + error("accept(): %s(%d)", strerror(errno), errno); + return; + } + + g_io_channel_set_close_on_unref(chan, TRUE); + + /* + * BNEP_SETUP_CONNECTION_REQUEST_MSG shall be received and + * user shall authorize the incomming connection before + * the time expires. + */ + to = g_malloc0(sizeof(struct timeout)); + to->id = g_timeout_add(SETUP_TIMEOUT, timeout_cb, to); + to->watch = g_io_add_watch_full(chan, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + bnep_setup, to, setup_destroy); + g_io_channel_unref(chan); + + return; +} + +int server_init(DBusConnection *conn, const char *iface_prefix, + gboolean secure) +{ + int lm; + + lm = secure ? L2CAP_LM_SECURE : 0; + + security = secure; + connection = dbus_connection_ref(conn); + prefix = iface_prefix; + + bnep_io = bt_l2cap_listen(BDADDR_ANY, BNEP_PSM, BNEP_MTU, lm, + connect_event, NULL); + if (!bnep_io) + return -1; + g_io_channel_set_close_on_unref(bnep_io, FALSE); + + if (bridge_create(BNEP_SVC_GN) < 0) + error("Can't create GN bridge"); + + return 0; +} + +void server_exit() +{ + if (bnep_io != NULL) { + g_io_channel_close(bnep_io); + g_io_channel_unref(bnep_io); + bnep_io = NULL; + } + + if (bridge_remove(BNEP_SVC_GN) < 0) + error("Can't remove GN bridge"); + + dbus_connection_unref(connection); + connection = NULL; +} + +static uint32_t register_server_record(struct network_server *ns) +{ + sdp_record_t *record; + + record = server_record_new(ns->name, ns->id); + if (!record) { + error("Unable to allocate new service record"); + return 0; + } + + if (add_record_to_server(&ns->src, record) < 0) { + error("Failed to register service record"); + sdp_record_free(record); + return 0; + } + + debug("register_server_record: got record id 0x%x", record->handle); + + return record->handle; +} + +static DBusMessage *get_uuid(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_server *ns = data; + DBusMessage *reply; + const char *uuid; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + uuid = bnep_uuid(ns->id); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_INVALID); + + return reply; +} + +static inline DBusMessage *failed(DBusMessage *msg, const char *description) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + description); +} + +static inline DBusMessage *invalid_arguments(DBusMessage *msg, + const char *description) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", + description); +} + +static DBusMessage *enable(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_server *ns = data; + DBusMessage *reply; + + if (ns->enable) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".AlreadyExist", + "Server already enabled"); + + if (bacmp(&ns->src, BDADDR_ANY) == 0) { + int dev_id; + + dev_id = hci_get_route(&ns->src); + if ((dev_id < 0) || (hci_devba(dev_id, &ns->src) < 0)) + return failed(msg, "Adapter not available"); + + /* Store the server info */ + server_store(ns->path); + } + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + /* Add the service record */ + ns->record_id = register_server_record(ns); + if (!ns->record_id) { + dbus_message_unref(reply); + return failed(msg, "Service record registration failed"); + } + + ns->enable = TRUE; + + store_property(&ns->src, ns->id, "enabled", "1"); + + g_dbus_emit_signal(conn, ns->path, NETWORK_SERVER_INTERFACE, + "Enabled", DBUS_TYPE_INVALID); + + return reply; +} + +static void kill_connection(void *data, void *udata) +{ + const char *address = data; + bdaddr_t dst; + + str2ba(address, &dst); + bnep_kill_connection(&dst); +} + +static DBusMessage *disable(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_server *ns = data; + DBusMessage *reply; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (!ns->enable) + return failed(msg, "Not enabled"); + + /* Remove the service record */ + if (ns->record_id) { + remove_record_from_server(ns->record_id); + ns->record_id = 0; + } + + ns->enable = FALSE; + + g_slist_foreach(ns->clients, (GFunc) kill_connection, NULL); + + store_property(&ns->src, ns->id, "enabled", "0"); + + g_dbus_emit_signal(conn, ns->path, NETWORK_SERVER_INTERFACE, + "Disabled", DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *is_enabled(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct network_server *ns = data; + DBusMessage *reply; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &ns->enable, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *set_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_server *ns = data; + DBusMessage *reply; + const char *name; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return NULL; + + if (!name || (strlen(name) == 0)) + return invalid_arguments(msg, "Invalid name"); + + if (ns->name) + g_free(ns->name); + ns->name = g_strdup(name); + + if (ns->enable && ns->record_id) { + uint32_t handle = register_server_record(ns); + if (!handle) { + dbus_message_unref(reply); + return failed(msg, + "Service record attribute update failed"); + } + + remove_record_from_server(ns->record_id); + ns->record_id = handle; + } + + store_property(&ns->src, ns->id, "name", ns->name); + + return reply; +} + +static DBusMessage *get_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_server *ns = data; + char name[] = ""; + const char *pname = (ns->name ? ns->name : name); + DBusMessage *reply; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &pname, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *set_address_range(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return NULL; +} + +static DBusMessage *set_routing(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct network_server *ns = data; + const char *iface; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_INVALID)) + return NULL; + + /* FIXME: Check if the interface is valid/UP */ + if (!iface || (strlen(iface) == 0)) + return invalid_arguments(msg, "Invalid interface"); + + if (ns->iface) + g_free(ns->iface); + ns->iface = g_strdup(iface); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *get_info(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_server *ns = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + const char *uuid; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + dbus_message_iter_append_dict_entry(&dict, "name", + DBUS_TYPE_STRING, &ns->name); + + uuid = bnep_uuid(ns->id); + dbus_message_iter_append_dict_entry(&dict, "uuid", + DBUS_TYPE_STRING, &uuid); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static void server_free(struct network_server *ns) +{ + if (!ns) + return; + + /* FIXME: Missing release/free all bnepX interfaces */ + if (ns->record_id) + remove_record_from_server(ns->record_id); + + if (ns->iface) + g_free(ns->iface); + + if (ns->name) + g_free(ns->name); + + if (ns->range) + g_free(ns->range); + + if (ns->path) + g_free(ns->path); + + if (ns->clients) { + g_slist_foreach(ns->clients, (GFunc) g_free, NULL); + g_slist_free(ns->clients); + } + + g_free(ns); +} + +static void server_unregister(void *data) +{ + struct network_server *ns = data; + + info("Unregistered server path:%s", ns->path); + + servers = g_slist_remove(servers, ns); + server_free(ns); +} + +static GDBusMethodTable server_methods[] = { + { "GetUUID", "", "s", get_uuid }, + { "Enable", "", "", enable }, + { "Disable", "", "", disable }, + { "IsEnabled", "", "b", is_enabled }, + { "SetName", "s", "", set_name }, + { "GetName", "", "s", get_name }, + { "SetAddressRange", "ss", "", set_address_range }, + { "SetRouting", "s", "", set_routing }, + { "GetInfo", "", "a{sv}",get_info }, + { } +}; + +static GDBusSignalTable server_signals[] = { + { "Enabled", "" }, + { "Disabled", "" }, + { } +}; + +int server_register(const char *path, bdaddr_t *src, uint16_t id) +{ + struct network_server *ns; + + if (!path) + return -EINVAL; + + ns = g_new0(struct network_server, 1); + + if (!g_dbus_register_interface(connection, path, + NETWORK_SERVER_INTERFACE, + server_methods, server_signals, NULL, + ns, server_unregister)) { + error("D-Bus failed to register %s interface", + NETWORK_SERVER_INTERFACE); + server_free(ns); + return -1; + } + + /* Setting a default name */ + if (id == BNEP_SVC_NAP) + ns->name = g_strdup("BlueZ NAP service"); + else if (id == BNEP_SVC_GN) + ns->name = g_strdup("BlueZ GN service"); + else + ns->name = g_strdup("BlueZ PANU service"); + + ns->path = g_strdup(path); + ns->id = id; + bacpy(&ns->src, src); + + servers = g_slist_append(servers, ns); + + info("Registered server path:%s", path); + + return 0; +} + +int server_register_from_file(const char *path, const bdaddr_t *src, + uint16_t id, const char *filename) +{ + struct network_server *ns; + char *str; + + if (!path) + return -EINVAL; + + ns = g_new0(struct network_server, 1); + + bacpy(&ns->src, src); + ns->path = g_strdup(path); + ns->id = id; + ns->name = textfile_get(filename, "name"); + if (!ns->name) { + /* Name is mandatory */ + server_free(ns); + return -1; + } + + ns->range = textfile_get(filename, "address_range"); + ns->iface = textfile_get(filename, "routing"); + + str = textfile_get(filename, "enabled"); + if (str) { + if (strcmp("1", str) == 0) { + ns->record_id = register_server_record(ns); + ns->enable = TRUE; + } + g_free(str); + } + + if (!g_dbus_register_interface(connection, path, + NETWORK_SERVER_INTERFACE, + server_methods, server_signals, NULL, + ns, server_unregister)) { + error("D-Bus failed to register %s interface", + NETWORK_SERVER_INTERFACE); + server_free(ns); + return -1; + } + + servers = g_slist_append(servers, ns); + + info("Registered server path:%s", path); + + return 0; +} + +int server_store(const char *path) +{ + struct network_server *ns; + char filename[PATH_MAX + 1]; + char addr[18]; + GSList *l; + + l = g_slist_find_custom(servers, path, find_server); + if (!l) { + error("Unable to salve %s on storage", path); + return -ENOENT; + } + + ns = l->data; + ba2str(&ns->src, addr); + if (ns->id == BNEP_SVC_NAP) + create_name(filename, PATH_MAX, STORAGEDIR, addr, "nap"); + else if (ns->id == BNEP_SVC_GN) + create_name(filename, PATH_MAX, STORAGEDIR, addr, "gn"); + else + create_name(filename, PATH_MAX, STORAGEDIR, addr, "panu"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + textfile_put(filename, "name", ns->name); + + if (ns->iface) + textfile_put(filename, "routing", ns->iface); + + if (ns->range) + textfile_put(filename, "range", ns->range); + + textfile_put(filename, "enabled", ns->enable ? "1": "0"); + + return 0; +} + +int server_find_data(const char *path, const char *pattern) +{ + struct network_server *ns; + const char *uuid; + GSList *l; + + l = g_slist_find_custom(servers, path, find_server); + if (!l) + return -1; + + ns = l->data; + if (ns->name && strcasecmp(pattern, ns->name) == 0) + return 0; + + if (ns->iface && strcasecmp(pattern, ns->iface) == 0) + return 0; + + uuid = bnep_name(ns->id); + if (uuid && strcasecmp(pattern, uuid) == 0) + return 0; + + if (bnep_service_id(pattern) == ns->id) + return 0; + + return -1; +} diff --git a/network/server.h b/network/server.h new file mode 100644 index 00000000..6fb06b58 --- /dev/null +++ b/network/server.h @@ -0,0 +1,33 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int server_init(DBusConnection *conn, const char *iface_prefix, + gboolean secure); +void server_exit(); +int server_register(const char *path, bdaddr_t *src, uint16_t id); +int server_register_from_file(const char *path, const bdaddr_t *src, + uint16_t id, const char *filename); + +int server_store(const char *path); + +int server_find_data(const char *path, const char *pattern); diff --git a/network/test-network b/network/test-network new file mode 100755 index 00000000..f428c25f --- /dev/null +++ b/network/test-network @@ -0,0 +1,37 @@ +#!/usr/bin/python + +import dbus + +bus = dbus.SystemBus() + +manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'), + 'org.bluez.Manager') + +conn = manager.ActivateService('network') + +network = dbus.Interface(bus.get_object(conn, '/org/bluez/network'), + 'org.bluez.network.Manager') + +try: + nap = dbus.Interface(bus.get_object(conn, network.FindServer('nap')), + 'org.bluez.network.Server') +except: + pass + +try: + gn = dbus.Interface(bus.get_object(conn, network.FindServer('gn')), + 'org.bluez.network.Server') +except: + pass + +try: + panu = dbus.Interface(bus.get_object(conn, network.FindServer('panu')), + 'org.bluez.network.Server') +except: + pass + +try: + client = dbus.Interface(bus.get_object(conn, network.LastConnection()), + 'org.bluez.network.Connection') +except: + pass |