| /* |
| * |
| * OBEX library with GLib integration |
| * |
| * Copyright (C) 2011 Intel Corporation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * 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 <sys/types.h> |
| #include <sys/socket.h> |
| #include <fcntl.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #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 : "<no err>"); |
| 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); |
| } |