// SPDX-License-Identifier: GPL-2.0-only /* * * OBEX library with GLib integration * * Copyright (C) 2011 Intel Corporation. All rights reserved. * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "gobex/gobex.h" #include "btio/btio.h" static GMainLoop *main_loop = NULL; static GSList *clients = NULL; static gboolean option_packet = FALSE; static gboolean option_bluetooth = FALSE; static int option_channel = -1; static int option_imtu = -1; static int option_omtu = -1; static char *option_root = NULL; static void sig_term(int sig) { g_print("Terminating due to signal %d\n", sig); g_main_loop_quit(main_loop); } static GOptionEntry options[] = { { "unix", 'u', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &option_bluetooth, "Use a UNIX socket" }, { "bluetooth", 'b', 0, G_OPTION_ARG_NONE, &option_bluetooth, "Use Bluetooth" }, { "channel", 'c', 0, G_OPTION_ARG_INT, &option_channel, "Transport channel", "CHANNEL" }, { "packet", 'p', 0, G_OPTION_ARG_NONE, &option_packet, "Packet based transport" }, { "stream", 's', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &option_packet, "Stream based transport" }, { "root", 'r', 0, G_OPTION_ARG_STRING, &option_root, "Root dir", "/..." }, { "input-mtu", 'i', 0, G_OPTION_ARG_INT, &option_imtu, "Transport input MTU", "MTU" }, { "output-mtu", 'o', 0, G_OPTION_ARG_INT, &option_omtu, "Transport output MTU", "MTU" }, { NULL }, }; static void disconn_func(GObex *obex, GError *err, gpointer user_data) { g_print("Client disconnected: %s\n", err ? err->message : ""); clients = g_slist_remove(clients, obex); g_obex_unref(obex); } struct transfer_data { int fd; }; static void transfer_complete(GObex *obex, GError *err, gpointer user_data) { struct transfer_data *data = user_data; if (err != NULL) g_printerr("transfer failed: %s\n", err->message); else g_print("transfer succeeded\n"); close(data->fd); g_free(data); } static gboolean recv_data(const void *buf, gsize len, gpointer user_data) { struct transfer_data *data = user_data; g_print("received %zu bytes of data\n", len); if (write(data->fd, buf, len) < 0) { g_printerr("write: %s\n", strerror(errno)); return FALSE; } return TRUE; } static void handle_put(GObex *obex, GObexPacket *req, gpointer user_data) { GError *err = NULL; GObexHeader *hdr; const char *type, *name; struct transfer_data *data; gsize type_len; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TYPE); if (hdr != NULL) { g_obex_header_get_bytes(hdr, (const guint8 **) &type, &type_len); if (type[type_len - 1] != '\0') { g_printerr("non-nul terminated type header\n"); type = NULL; } } else type = NULL; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_NAME); if (hdr != NULL) g_obex_header_get_unicode(hdr, &name); else name = NULL; g_print("put type \"%s\" name \"%s\"\n", type ? type : "", name ? name : ""); data = g_new0(struct transfer_data, 1); data->fd = open(name, O_WRONLY | O_CREAT | O_NOCTTY, 0600); if (data->fd < 0) { g_printerr("open(%s): %s\n", name, strerror(errno)); g_free(data); g_obex_send_rsp(obex, G_OBEX_RSP_FORBIDDEN, NULL, G_OBEX_HDR_INVALID); return; } g_obex_put_rsp(obex, req, recv_data, transfer_complete, data, &err, G_OBEX_HDR_INVALID); if (err != NULL) { g_printerr("Unable to send response: %s\n", err->message); g_error_free(err); g_free(data); } } static gssize send_data(void *buf, gsize len, gpointer user_data) { struct transfer_data *data = user_data; gssize ret; ret = read(data->fd, buf, len); g_print("sending %zu bytes of data\n", ret); return ret; } static void handle_get(GObex *obex, GObexPacket *req, gpointer user_data) { GError *err = NULL; struct transfer_data *data; const char *type, *name; GObexHeader *hdr; gsize type_len; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TYPE); if (hdr != NULL) { g_obex_header_get_bytes(hdr, (const guint8 **) &type, &type_len); if (type[type_len - 1] != '\0') { g_printerr("non-nul terminated type header\n"); type = NULL; } } else type = NULL; hdr = g_obex_packet_get_header(req, G_OBEX_HDR_NAME); if (hdr != NULL) g_obex_header_get_unicode(hdr, &name); else name = NULL; g_print("get type \"%s\" name \"%s\"\n", type ? type : "", name ? name : ""); data = g_new0(struct transfer_data, 1); data->fd = open(name, O_RDONLY | O_NOCTTY, 0); if (data->fd < 0) { g_printerr("open(%s): %s\n", name, strerror(errno)); g_free(data); g_obex_send_rsp(obex, G_OBEX_RSP_FORBIDDEN, NULL, G_OBEX_HDR_INVALID); return; } g_obex_get_rsp(obex, send_data, transfer_complete, data, &err, G_OBEX_HDR_INVALID); if (err != NULL) { g_printerr("Unable to send response: %s\n", err->message); g_error_free(err); g_free(data); } } static void handle_connect(GObex *obex, GObexPacket *req, gpointer user_data) { GObexPacket *rsp; g_print("connect\n"); rsp = g_obex_packet_new(G_OBEX_RSP_SUCCESS, TRUE, G_OBEX_HDR_INVALID); g_obex_send(obex, rsp, NULL); } static void transport_accept(GIOChannel *io) { GObex *obex; GObexTransportType transport; g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); g_io_channel_set_close_on_unref(io, TRUE); if (option_packet) transport = G_OBEX_TRANSPORT_PACKET; else transport = G_OBEX_TRANSPORT_STREAM; obex = g_obex_new(io, transport, option_imtu, option_omtu); g_obex_set_disconnect_function(obex, disconn_func, NULL); g_obex_add_request_function(obex, G_OBEX_OP_PUT, handle_put, NULL); g_obex_add_request_function(obex, G_OBEX_OP_GET, handle_get, NULL); g_obex_add_request_function(obex, G_OBEX_OP_CONNECT, handle_connect, NULL); clients = g_slist_append(clients, obex); } static gboolean unix_accept(GIOChannel *chan, GIOCondition cond, gpointer data) { struct sockaddr_un addr; socklen_t addrlen; int sk, cli_sk; GIOChannel *io; if (cond & G_IO_NVAL) return FALSE; if (cond & (G_IO_HUP | G_IO_ERR)) { g_io_channel_shutdown(chan, TRUE, NULL); return FALSE; } sk = g_io_channel_unix_get_fd(chan); memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); cli_sk = accept(sk, (struct sockaddr *) &addr, &addrlen); if (cli_sk < 0) { g_printerr("accept: %s (%d)\n", strerror(errno), errno); return TRUE; } g_print("Accepted new client connection on unix socket (fd=%d)\n", cli_sk); io = g_io_channel_unix_new(cli_sk); transport_accept(io); g_io_channel_unref(io); return TRUE; } static void bluetooth_accept(GIOChannel *io, GError *err, gpointer data) { if (err) { g_printerr("accept: %s\n", err->message); return; } g_print("Accepted new client connection on bluetooth socket\n"); transport_accept(io); } static gboolean bluetooth_watch(GIOChannel *chan, GIOCondition cond, gpointer data) { if (cond & G_IO_NVAL) return FALSE; g_io_channel_shutdown(chan, TRUE, NULL); return FALSE; } static GIOChannel *l2cap_listen(GError **err) { return bt_io_listen(bluetooth_accept, NULL, NULL, NULL, err, BT_IO_OPT_PSM, option_channel, BT_IO_OPT_MODE, BT_IO_MODE_ERTM, BT_IO_OPT_OMTU, option_omtu, BT_IO_OPT_IMTU, option_imtu, BT_IO_OPT_INVALID); } static GIOChannel *rfcomm_listen(GError **err) { return bt_io_listen(bluetooth_accept, NULL, NULL, NULL, err, BT_IO_OPT_CHANNEL, option_channel, BT_IO_OPT_INVALID); } static guint bluetooth_listen(void) { GIOChannel *io; guint id; GError *err = NULL; if (option_channel == -1) { g_printerr("Bluetooth channel not set\n"); return 0; } if (option_packet || option_channel > 31) io = l2cap_listen(&err); else io = rfcomm_listen(&err); if (io == NULL) { g_printerr("%s\n", err->message); g_error_free(err); return 0; } g_print("Bluetooth socket created\n"); id = g_io_add_watch(io, G_IO_HUP | G_IO_ERR | G_IO_NVAL, bluetooth_watch, NULL); g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); g_io_channel_set_close_on_unref(io, TRUE); g_io_channel_unref(io); return id; } static guint unix_listen(void) { GIOChannel *io; struct sockaddr_un addr = { AF_UNIX, "\0/gobex/server" }; int sk, err, sock_type; guint id; if (option_packet) sock_type = SOCK_SEQPACKET; else sock_type = SOCK_STREAM; sk = socket(PF_LOCAL, sock_type, 0); if (sk < 0) { err = errno; g_printerr("Can't create unix socket: %s (%d)\n", strerror(err), err); return 0; } if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { g_printerr("Can't bind unix socket: %s (%d)\n", strerror(errno), errno); close(sk); return 0; } if (listen(sk, 1) < 0) { g_printerr("Can't listen on unix socket: %s (%d)\n", strerror(errno), errno); close(sk); return 0; } g_print("Unix socket created: %d\n", sk); io = g_io_channel_unix_new(sk); id = g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, unix_accept, NULL); g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); g_io_channel_set_close_on_unref(io, TRUE); g_io_channel_unref(io); return id; } int main(int argc, char *argv[]) { GOptionContext *context; GError *err = NULL; struct sigaction sa; guint server_id; context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); g_option_context_parse(context, &argc, &argv, &err); if (err != NULL) { g_printerr("%s\n", err->message); g_error_free(err); exit(EXIT_FAILURE); } if (option_root && chdir(option_root) < 0) { perror("chdir:"); exit(EXIT_FAILURE); } if (option_bluetooth) server_id = bluetooth_listen(); else server_id = unix_listen(); if (server_id == 0) exit(EXIT_FAILURE); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_term; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); main_loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(main_loop); g_source_remove(server_id); g_slist_free_full(clients, (GDestroyNotify) g_obex_unref); g_option_context_free(context); g_main_loop_unref(main_loop); exit(EXIT_SUCCESS); }