From 6984ba9a5745a34c3206274880ffa1f93baef679 Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Sat, 1 Dec 2012 15:26:48 +0100 Subject: [PATCH] obexd: Include initial source --- obexd/src/bluetooth.c | 299 +++++++++++++++++ obexd/src/bluetooth.h | 32 ++ obexd/src/dbus.h | 36 ++ obexd/src/ftp.c | 203 +++++++++++ obexd/src/logging.c | 219 ++++++++++++ obexd/src/logging.h | 32 ++ obexd/src/main.c | 221 ++++++++++++ obexd/src/manager.c | 368 ++++++++++++++++++++ obexd/src/obex.c | 765 ++++++++++++++++++++++++++++++++++++++++++ obexd/src/obex.h | 76 +++++ obexd/src/obexd.h | 38 +++ obexd/src/opp.c | 189 +++++++++++ 12 files changed, 2478 insertions(+) create mode 100644 obexd/src/bluetooth.c create mode 100644 obexd/src/bluetooth.h create mode 100644 obexd/src/dbus.h create mode 100644 obexd/src/ftp.c create mode 100644 obexd/src/logging.c create mode 100644 obexd/src/logging.h create mode 100644 obexd/src/main.c create mode 100644 obexd/src/manager.c create mode 100644 obexd/src/obex.c create mode 100644 obexd/src/obex.h create mode 100644 obexd/src/obexd.h create mode 100644 obexd/src/opp.c diff --git a/obexd/src/bluetooth.c b/obexd/src/bluetooth.c new file mode 100644 index 000000000..48e7936cc --- /dev/null +++ b/obexd/src/bluetooth.c @@ -0,0 +1,299 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Nokia Corporation + * Copyright (C) 2007-2008 Instituto Nokia de Tecnologia (INdT) + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "logging.h" +#include "obex.h" + +static GSList *handles = NULL; +static sdp_session_t *session = NULL; + +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 uint32_t register_record(const gchar *name, + guint16 service, guint8 channel) +{ + uuid_t root_uuid, uuid, l2cap_uuid, rfcomm_uuid, obex_uuid; + sdp_list_t *root, *svclass_id, *apseq, *profiles, *aproto, *proto[3]; + sdp_data_t *sdp_data; + sdp_profile_desc_t profile; + sdp_record_t record; + uint8_t formats = 0xFF; + int ret; + + switch (service) { + case OBEX_OPUSH: + sdp_uuid16_create(&uuid, OBEX_OBJPUSH_SVCLASS_ID); + sdp_uuid16_create(&profile.uuid, OBEX_OBJPUSH_PROFILE_ID); + break; + case OBEX_FTP: + sdp_uuid16_create(&uuid, OBEX_FILETRANS_SVCLASS_ID); + sdp_uuid16_create(&profile.uuid, OBEX_FILETRANS_PROFILE_ID); + break; + default: + return 0; + } + + /* Browse Groups */ + memset(&record, 0, sizeof(sdp_record_t)); + record.handle = 0xffffffff; + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(&record, root); + sdp_list_free(root, NULL); + + /* Service Class */ + svclass_id = sdp_list_append(NULL, &uuid); + sdp_set_service_classes(&record, svclass_id); + sdp_list_free(svclass_id, NULL); + + /* Profile Descriptor */ + profile.version = 0x0100; + profiles = sdp_list_append(NULL, &profile); + sdp_set_profile_descs(&record, profiles); + sdp_list_free(profiles, NULL); + + /* Protocol Descriptor */ + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap_uuid); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(NULL, &rfcomm_uuid); + sdp_data = sdp_data_alloc(SDP_UINT8, &channel); + proto[1] = sdp_list_append(proto[1], sdp_data); + apseq = sdp_list_append(apseq, proto[1]); + + sdp_uuid16_create(&obex_uuid, OBEX_UUID); + proto[2] = sdp_list_append(NULL, &obex_uuid); + apseq = sdp_list_append(apseq, proto[2]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(&record, aproto); + + sdp_data_free(sdp_data); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(proto[2], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto, NULL); + + /* Suported Repositories */ + if (service == OBEX_OPUSH) + sdp_attr_add_new(&record, SDP_ATTR_SUPPORTED_FORMATS_LIST, + SDP_UINT8, &formats); + + /* Service Name */ + sdp_set_info_attr(&record, name, NULL, NULL); + + add_lang_attr(&record); + + ret = sdp_record_register(session, &record, SDP_RECORD_PERSIST); + + sdp_list_free(record.attrlist, (sdp_free_func_t) sdp_data_free); + sdp_list_free(record.pattern, free); + + return (ret < 0 ? 0 : record.handle); +} + +static gboolean connect_event(GIOChannel *io, GIOCondition cond, gpointer user_data) +{ + struct sockaddr_rc raddr; + socklen_t alen; + struct server *server = user_data; + gchar address[18]; + gint err, sk, nsk; + + sk = g_io_channel_unix_get_fd(io); + alen = sizeof(raddr); + nsk = accept(sk, (struct sockaddr *) &raddr, &alen); + if (nsk < 0) + return TRUE; + + alen = sizeof(raddr); + if (getpeername(nsk, (struct sockaddr *)&raddr, &alen) < 0) { + err = errno; + error("getpeername(): %s(%d)", strerror(err), err); + close(nsk); + return TRUE; + } + + ba2str(&raddr.rc_bdaddr, address); + info("New connection from: %s channel: %d", address, raddr.rc_channel); + + if (obex_session_start(nsk, server) < 0) + close(nsk); + + return TRUE; +} + +static void server_destroyed(gpointer user_data) +{ + struct server *server = user_data; + + error("Server destroyed"); + + g_free(server->folder); + g_free(server); +} + +static gint server_register(guint16 service, const gchar *name, + guint8 channel, const gchar *folder, gboolean auto_accept) +{ + struct sockaddr_rc laddr; + GIOChannel *io; + gint err, sk, arg; + struct server *server; + uint32_t *handle; + + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + err = errno; + error("socket(): %s(%d)", strerror(err), err); + return -err; + } + + arg = fcntl(sk, F_GETFL); + if (arg < 0) { + err = errno; + goto failed; + } + + arg |= O_NONBLOCK; + if (fcntl(sk, F_SETFL, arg) < 0) { + err = errno; + goto failed; + } + + memset(&laddr, 0, sizeof(laddr)); + laddr.rc_family = AF_BLUETOOTH; + bacpy(&laddr.rc_bdaddr, BDADDR_ANY); + laddr.rc_channel = channel; + + if (bind(sk, (struct sockaddr *) &laddr, sizeof(laddr)) < 0) { + err = errno; + goto failed; + } + + if (listen(sk, 10) < 0) { + err = errno; + goto failed; + } + + handle = malloc(sizeof(uint32_t)); + *handle = register_record(name, service, channel); + if (*handle == 0) { + g_free(handle); + err = EIO; + goto failed; + } + + handles = g_slist_prepend(handles, handle); + + server = g_malloc0(sizeof(struct server)); + server->service = service; + server->folder = g_strdup(folder); + server->auto_accept = auto_accept; + + io = g_io_channel_unix_new(sk); + g_io_channel_set_close_on_unref(io, TRUE); + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + connect_event, server, server_destroyed); + g_io_channel_unref(io); + + debug("Registered: %s, record handle: 0x%x, folder: %s", name, *handle, folder); + + return 0; + +failed: + error("Bluetooth server register failed: %s(%d)", strerror(err), err); + close(sk); + + return -err; +} + +gint bluetooth_init(guint service, const gchar *name, const gchar *folder, + guint8 channel, gboolean auto_accept) +{ + if (!session) { + session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY); + if (!session) { + gint err = errno; + error("sdp_connect(): %s(%d)", strerror(err), err); + return -err; + } + } + + return server_register(service, name, channel, folder, auto_accept); +} + +static void unregister_record(gpointer rec_handle, gpointer user_data) +{ + uint32_t *handle = rec_handle; + + sdp_device_record_unregister_binary(session, BDADDR_ANY, *handle); + g_free(handle); +} + +void bluetooth_exit(void) +{ + g_slist_foreach(handles, unregister_record, NULL); + g_slist_free(handles); + + sdp_close(session); +} diff --git a/obexd/src/bluetooth.h b/obexd/src/bluetooth.h new file mode 100644 index 000000000..9e7973b2d --- /dev/null +++ b/obexd/src/bluetooth.h @@ -0,0 +1,32 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Nokia Corporation + * Copyright (C) 2007-2008 Instituto Nokia de Tecnologia (INdT) + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +gint bluetooth_init(guint service, const gchar *name, const gchar *folder, + guint8 channel, gboolean auto_accept); +void bluetooth_exit(void); diff --git a/obexd/src/dbus.h b/obexd/src/dbus.h new file mode 100644 index 000000000..a4234c2b6 --- /dev/null +++ b/obexd/src/dbus.h @@ -0,0 +1,36 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Instituto Nokia de Tecnologia (INdT) + * + * + * 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 + * + */ + +void emit_transfer_started(guint32 id); + +void emit_transfer_completed(guint32 id, gboolean success); + +void emit_transfer_progress(guint32 id, guint32 total, guint32 transfered); + +int request_authorization(gint32 cid, int fd, const gchar *filename, + const gchar *type, gint32 length, gint32 time, + gchar **new_folder, gchar **new_name); + +void register_transfer(guint32 id); + +void unregister_transfer(guint32 id); diff --git a/obexd/src/ftp.c b/obexd/src/ftp.c new file mode 100644 index 000000000..04ab6237d --- /dev/null +++ b/obexd/src/ftp.c @@ -0,0 +1,203 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Nokia Corporation + * Copyright (C) 2007-2008 Instituto Nokia de Tecnologia (INdT) + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "logging.h" +#include "obex.h" +#include "logging.h" + +#define LST_TYPE "x-obex/folder-listing" +#define CAP_TYPE "x-obex/capability" + +#define CAP_FILE CONFIGDIR "/capability.xml" + +static gboolean get_by_type(struct obex_session *os, gchar *type, guint32 *size) +{ + if (g_str_equal(type, CAP_TYPE)) + return os_prepare_get(os, CAP_FILE, size); + + return FALSE; +} + +void ftp_get(obex_t *obex, obex_object_t *obj) +{ + obex_headerdata_t hv; + struct obex_session *os; + guint32 size; + + os = OBEX_GetUserData(obex); + if (os == NULL) + return; + + if (os->current_folder == NULL) + goto fail; + + if (os->name) { + gboolean ret; + gchar *path = g_build_filename(os->current_folder, + os->name, NULL); + ret = os_prepare_get(os, path, &size); + + g_free(path); + + if (!ret) + goto fail; + } else if (os->type) { + if (!get_by_type(os, os->type, &size)) + goto fail; + } else + goto fail; + + hv.bq4 = size; + OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_LENGTH, hv, 4, 0); + + /* Add body header */ + hv.bs = NULL; + if (size == 0) + OBEX_ObjectAddHeader (obex, obj, OBEX_HDR_BODY, + hv, 0, OBEX_FL_FIT_ONE_PACKET); + else + OBEX_ObjectAddHeader (obex, obj, OBEX_HDR_BODY, + hv, 0, OBEX_FL_STREAM_START); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, + OBEX_RSP_SUCCESS); + + return; + +fail: + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + + return; +} + +void ftp_put(obex_t *obex, obex_object_t *obj) +{ + OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED, + OBEX_RSP_NOT_IMPLEMENTED); +} + +void ftp_setpath(obex_t *obex, obex_object_t *obj) +{ + struct obex_session *os; + guint8 *nonhdr; + gchar *fullname; + + os = OBEX_GetUserData(obex); + + if (OBEX_ObjectGetNonHdrData(obj, &nonhdr) != 2) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, + OBEX_RSP_PRECONDITION_FAILED); + error("Set path failed: flag and constants not found!"); + return; + } + + /* Check flag "Backup" */ + if ((nonhdr[0] & 0x01) == 0x01) { + + debug("Set to parent path"); + + if (strcmp(os->server->folder, os->current_folder) == 0) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + return; + } + + fullname = g_path_get_dirname(os->current_folder); + g_free(os->current_folder); + os->current_folder = g_strdup(fullname); + g_free(fullname); + + debug("Set to parent path: %s", os->current_folder); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS); + return; + } + + if (!os->name) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_BAD_REQUEST); + debug("Set path failed: name missing!"); + return; + } + + if (strlen(os->name) == 0) { + debug("Set to root"); + g_free(os->current_folder); + os->current_folder = g_strdup(os->server->folder); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS); + return; + } + + /* Check and set to name path */ + if (strstr(os->name, "/") || strcmp(os->name, "..") == 0) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + error("Set path failed: name incorrect!"); + return; + } + + fullname = g_build_filename(os->current_folder, os->name, NULL); + + debug("Fullname: %s", fullname); + + if (g_file_test(fullname, G_FILE_TEST_IS_DIR)) { + g_free(os->current_folder); + os->current_folder = g_strdup(fullname); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS); + goto done; + } + + if (!g_file_test(fullname, G_FILE_TEST_EXISTS) && nonhdr[0] == 0 && + mkdir(fullname, 0755) >= 0) { + g_free(os->current_folder); + os->current_folder = g_strdup(fullname); + OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS); + goto done; + + } + + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); +done: + g_free(fullname); +} diff --git a/obexd/src/logging.c b/obexd/src/logging.c new file mode 100644 index 000000000..c1e489cd9 --- /dev/null +++ b/obexd/src/logging.c @@ -0,0 +1,219 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include + +#include + +static volatile int debug_enabled = 0; + +static inline void vinfo(const char *format, va_list ap) +{ + vsyslog(LOG_INFO, format, ap); +} + +void info(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vinfo(format, ap); + + va_end(ap); +} + +void error(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vsyslog(LOG_ERR, format, ap); + + va_end(ap); +} + +void debug(const char *format, ...) +{ + va_list ap; + + if (!debug_enabled) + return; + + va_start(ap, format); + + vsyslog(LOG_DEBUG, format, ap); + + va_end(ap); +} + +void toggle_debug(void) +{ + debug_enabled = (debug_enabled + 1) % 2; +} + +void enable_debug(void) +{ + debug_enabled = 1; +} + +void disable_debug(void) +{ + debug_enabled = 0; +} + +void start_logging(const char *ident, const char *message, ...) +{ + va_list ap; + + openlog(ident, LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); + + va_start(ap, message); + + vinfo(message, ap); + + va_end(ap); +} + +void stop_logging(void) +{ + closelog(); +} + +static struct { + int evt; + const char *name; +} obex_event[] = { + { OBEX_EV_PROGRESS, "PROGRESS" }, /* Progress has been made */ + { OBEX_EV_REQHINT, "REQHINT" }, /* An incoming request is about to come */ + { OBEX_EV_REQ, "REQ" }, /* An incoming request has arrived */ + { OBEX_EV_REQDONE, "REQDONE" }, /* Request has finished */ + { OBEX_EV_LINKERR, "LINKERR" }, /* Link has been disconnected */ + { OBEX_EV_PARSEERR, "PARSEERR" }, /* Malformed data encountered */ + { OBEX_EV_ACCEPTHINT, "ACCEPTHINT" }, /* Connection accepted */ + { OBEX_EV_ABORT, "ABORT" }, /* Request was aborted */ + { OBEX_EV_STREAMEMPTY, "STREAMEMPTY" }, /* Need to feed more data when sending a stream */ + { OBEX_EV_STREAMAVAIL, "STREAMAVAIL" }, /* Time to pick up data when receiving a stream */ + { OBEX_EV_UNEXPECTED, "UNEXPECTED" }, /* Unexpected data, not fatal */ + { OBEX_EV_REQCHECK, "REQCHECK" }, /* First packet of an incoming request has been parsed */ + { 0xFF, NULL }, +}; + +/* Possible commands */ +static struct { + int cmd; + const char *name; +} obex_command[] = { + { OBEX_CMD_CONNECT, "CONNECT" }, + { OBEX_CMD_DISCONNECT, "DISCONNECT" }, + { OBEX_CMD_PUT, "PUT" }, + { OBEX_CMD_GET, "GET" }, + { OBEX_CMD_SETPATH, "SETPATH" }, + { OBEX_CMD_SESSION, "SESSION" }, + { OBEX_CMD_ABORT, "ABORT" }, + { OBEX_FINAL, "FINAL" }, + { 0xFF, NULL }, +}; + +/* Possible Response */ +static struct { + int rsp; + const char *name; +} obex_response[] = { + { OBEX_RSP_CONTINUE, "CONTINUE" }, + { OBEX_RSP_SWITCH_PRO, "SWITCH_PRO" }, + { OBEX_RSP_SUCCESS, "SUCCESS" }, + { OBEX_RSP_CREATED, "CREATED" }, + { OBEX_RSP_ACCEPTED, "ACCEPTED" }, + { OBEX_RSP_NON_AUTHORITATIVE, "NON_AUTHORITATIVE" }, + { OBEX_RSP_NO_CONTENT, "NO_CONTENT" }, + { OBEX_RSP_RESET_CONTENT, "RESET_CONTENT" }, + { OBEX_RSP_PARTIAL_CONTENT, "PARTIAL_CONTENT" }, + { OBEX_RSP_MULTIPLE_CHOICES, "MULTIPLE_CHOICES" }, + { OBEX_RSP_MOVED_PERMANENTLY, "MOVED_PERMANENTLY" }, + { OBEX_RSP_MOVED_TEMPORARILY, "MOVED_TEMPORARILY" }, + { OBEX_RSP_SEE_OTHER, "SEE_OTHER" }, + { OBEX_RSP_NOT_MODIFIED, "NOT_MODIFIED" }, + { OBEX_RSP_USE_PROXY, "USE_PROXY" }, + { OBEX_RSP_BAD_REQUEST, "BAD_REQUEST" }, + { OBEX_RSP_UNAUTHORIZED, "UNAUTHORIZED" }, + { OBEX_RSP_PAYMENT_REQUIRED, "PAYMENT_REQUIRED" }, + { OBEX_RSP_FORBIDDEN, "FORBIDDEN" }, + { OBEX_RSP_NOT_FOUND, "NOT_FOUND" }, + { OBEX_RSP_METHOD_NOT_ALLOWED, "METHOD_NOT_ALLOWED" }, + { OBEX_RSP_NOT_ACCEPTABLE, "NOT_ACCEPTABLE" }, + { OBEX_RSP_PROXY_AUTH_REQUIRED, "PROXY_AUTH_REQUIRED" }, + { OBEX_RSP_REQUEST_TIME_OUT, "REQUEST_TIME_OUT" }, + { OBEX_RSP_CONFLICT, "CONFLICT" }, + { OBEX_RSP_GONE, "GONE" }, + { OBEX_RSP_LENGTH_REQUIRED, "LENGTH_REQUIRED" }, + { OBEX_RSP_PRECONDITION_FAILED, "PRECONDITION_FAILED" }, + { OBEX_RSP_REQ_ENTITY_TOO_LARGE, "REQ_ENTITY_TOO_LARGE" }, + { OBEX_RSP_REQ_URL_TOO_LARGE, "REQ_URL_TOO_LARGE" }, + { OBEX_RSP_UNSUPPORTED_MEDIA_TYPE, "UNSUPPORTED_MEDIA_TYPE"}, + { OBEX_RSP_INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR" }, + { OBEX_RSP_NOT_IMPLEMENTED, "NOT_IMPLEMENTED" }, + { OBEX_RSP_BAD_GATEWAY, "BAD_GATEWAY" }, + { OBEX_RSP_SERVICE_UNAVAILABLE, "SERVICE_UNAVAILABLE" }, + { OBEX_RSP_GATEWAY_TIMEOUT, "GATEWAY_TIMEOUT" }, + { OBEX_RSP_VERSION_NOT_SUPPORTED, "VERSION_NOT_SUPPORTED" }, + { OBEX_RSP_DATABASE_FULL, "DATABASE_FULL" }, + { OBEX_RSP_DATABASE_LOCKED, "DATABASE_LOCKED" }, + { 0xFF, NULL }, +}; + +void obex_debug(int evt, int cmd, int rsp) +{ + const char *evtstr = NULL, *cmdstr = NULL, *rspstr = NULL; + int i; + + if (!debug_enabled) + return; + + for (i = 0; obex_event[i].evt != 0xFF; i++) { + if (obex_event[i].evt != evt) + continue; + evtstr = obex_event[i].name; + } + + for (i = 0; obex_command[i].cmd != 0xFF; i++) { + if (obex_command[i].cmd != cmd) + continue; + cmdstr = obex_command[i].name; + } + + for (i = 0; obex_response[i].rsp != 0xFF; i++) { + if (obex_response[i].rsp != rsp) + continue; + rspstr = obex_response[i].name; + } + + debug("%s(0x%x), %s(0x%x), %s(0x%x)", evtstr, evt, cmdstr, cmd, rspstr, rsp); +} diff --git a/obexd/src/logging.h b/obexd/src/logging.h new file mode 100644 index 000000000..f30628a43 --- /dev/null +++ b/obexd/src/logging.h @@ -0,0 +1,32 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 + * + */ + +void info(const char *format, ...); +void error(const char *format, ...); +void debug(const char *format, ...); +void obex_debug(int evt, int cmd, int rsp); +void toggle_debug(void); +void enable_debug(void); +void disable_debug(void); +void start_logging(const char *ident, const char *message, ...); +void stop_logging(void); diff --git a/obexd/src/main.c b/obexd/src/main.c new file mode 100644 index 000000000..1ff0a0c48 --- /dev/null +++ b/obexd/src/main.c @@ -0,0 +1,221 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "logging.h" +#include "bluetooth.h" +#include "obexd.h" +#include "obex.h" + +#define OPUSH_CHANNEL 9 +#define FTP_CHANNEL 10 + +#define DEFAULT_ROOT_PATH "/tmp" + +static GMainLoop *main_loop = NULL; + +static int server_start(int service, const char *root_path, + gboolean auto_accept) +{ + /* FIXME: Necessary check enabled transports(Bluetooth/USB) */ + + switch (service) { + case OBEX_OPUSH: + bluetooth_init(OBEX_OPUSH, "OBEX OPUSH server", + root_path, OPUSH_CHANNEL, auto_accept); + break; + case OBEX_FTP: + bluetooth_init(OBEX_FTP, "OBEX FTP server", + root_path, FTP_CHANNEL, auto_accept); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void server_stop() +{ + /* FIXME: If Bluetooth enabled */ + bluetooth_exit(); +} + +static void sig_term(int sig) +{ + g_main_loop_quit(main_loop); +} + +static void usage(void) +{ + printf("OBEX Server version %s\n\n", VERSION); + + printf("Usage:\n" + "\tobexd [options] \n" + "\n"); + + printf("Options:\n" + "\t-n, --nodaemon Don't fork daemon to background\n" + "\t-d, --debug Enable output of debug information\n" + "\t-r, --root Specify root folder location\n" + "\t-a, --auto-accept Automatically accept push requests\n" + "\t-h, --help Display help\n"); + printf("Servers:\n" + "\t-o, --opp Enable OPP server\n" + "\t-f, --ftp Enable FTP server\n" + "\n"); +} + +static struct option options[] = { + { "nodaemon", 0, 0, 'n' }, + { "debug", 0, 0, 'd' }, + { "ftp", 0, 0, 'f' }, + { "opp", 0, 0, 'o' }, + { "help", 0, 0, 'h' }, + { "root", 1, 0, 'r' }, + { "auto-accept", 0, 0, 'a' }, + { } +}; + +int main(int argc, char *argv[]) +{ + DBusConnection *conn; + DBusError err; + struct sigaction sa; + int log_option = LOG_NDELAY | LOG_PID; + int opt, detach = 1, debug = 0, opush = 0, ftp = 0, auto_accept = 0; + const char *root_path = DEFAULT_ROOT_PATH; + + while ((opt = getopt_long(argc, argv, "+ndhofr:a", options, NULL)) != EOF) { + switch(opt) { + case 'n': + detach = 0; + break; + case 'd': + debug = 1; + break; + case 'h': + case 'o': + opush = 1; + break; + case 'f': + ftp = 1; + break; + case 'r': + root_path = optarg; + break; + case 'a': + auto_accept = 1; + break; + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (!(opush || ftp)) { + fprintf(stderr, "No server selected (use either " + "--opp or --ftp or both)\n"); + exit(1); + } + + if (detach) { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } else + log_option |= LOG_PERROR; + + openlog("obexd", log_option, LOG_DAEMON); + + if (debug) { + info("Enabling debug information"); + enable_debug(); + } + + main_loop = g_main_loop_new(NULL, FALSE); + + dbus_error_init(&err); + + conn = g_dbus_setup_bus(DBUS_BUS_SESSION, OPENOBEX_SERVICE, &err); + if (conn == NULL) { + if (dbus_error_is_set(&err) == TRUE) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } else + fprintf(stderr, "Can't register with session bus\n"); + exit(1); + } + + if (opush) + server_start(OBEX_OPUSH, root_path, auto_accept); + + if (ftp) + server_start(OBEX_FTP, root_path, auto_accept); + + if (!manager_init(conn)) + goto fail; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sig_term; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + g_main_loop_run(main_loop); + + manager_cleanup(); + + server_stop(); + +fail: + dbus_connection_unref(conn); + + g_main_loop_unref(main_loop); + + closelog(); + + return 0; +} diff --git a/obexd/src/manager.c b/obexd/src/manager.c new file mode 100644 index 000000000..e165c80e3 --- /dev/null +++ b/obexd/src/manager.c @@ -0,0 +1,368 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include "obexd.h" +#include "logging.h" + +#define TRANSFER_INTERFACE OPENOBEX_SERVICE ".Transfer" + +#define TIMEOUT 60*1000 /* Timeout for user response (miliseconds) */ + +struct agent { + gchar *bus_name; + gchar *path; + gboolean auth_pending; + gchar *new_name; + gchar *new_folder; +}; + +static struct agent *agent = NULL; + +static void agent_free(struct agent *agent) +{ + g_free(agent->new_folder); + g_free(agent->new_name); + g_free(agent->bus_name); + g_free(agent->path); + g_free(agent); +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static inline DBusMessage *agent_already_exists(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Agent already exists"); +} + +static inline DBusMessage *agent_does_not_exist(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Agent does not exist"); +} + +static inline DBusMessage *not_authorized(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAuthorized", + "Not authorized"); +} + +static void agent_disconnected(void *user_data) +{ + debug("Agent exited"); + agent_free(agent); + agent = NULL; +} + +static DBusMessage *register_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const gchar *path, *sender; + + if (agent) + return agent_already_exists(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + sender = dbus_message_get_sender(msg); + agent = g_new0(struct agent, 1); + agent->bus_name = g_strdup(sender); + agent->path = g_strdup(path); + + g_dbus_add_disconnect_watch(conn, sender, + agent_disconnected, NULL, NULL); + + debug("Agent registered"); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const gchar *path, *sender; + + if (!agent) + return agent_does_not_exist(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (strcmp(agent->path, path) != 0) + return agent_does_not_exist(msg); + + sender = dbus_message_get_sender(msg); + if (strcmp(agent->bus_name, sender) != 0) + return not_authorized(msg); + + agent_free(agent); + agent = NULL; + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable manager_methods[] = { + { "RegisterAgent", "o", "", register_agent }, + { "UnregisterAgent", "o", "", unregister_agent }, + { } +}; + +static GDBusSignalTable manager_signals[] = { + { "TransferStarted", "o" }, + { "TransferCompleted", "ob" }, + { } +}; + +static GDBusMethodTable transfer_methods[] = { + { "Cancel", "" }, + { } +}; + +static GDBusSignalTable transfer_signals[] = { + { "Progress", "ii" }, + { } +}; + +static DBusConnection *connection = NULL; + +gboolean manager_init(DBusConnection *conn) +{ + DBG("conn %p", conn); + + connection = dbus_connection_ref(conn); + if (connection == NULL) + return FALSE; + + return g_dbus_register_interface(connection, OPENOBEX_MANAGER_PATH, + OPENOBEX_MANAGER_INTERFACE, + manager_methods, manager_signals, NULL, + NULL, NULL); +} + +void manager_cleanup(void) +{ + DBG("conn %p", connection); + + g_dbus_unregister_interface(connection, OPENOBEX_MANAGER_PATH, + OPENOBEX_MANAGER_INTERFACE); + + /* FIXME: Release agent? */ + + if (agent) + agent_free(agent); + + dbus_connection_unref(connection); +} + +void emit_transfer_started(guint32 id) +{ + gchar *path = g_strdup_printf("/transfer%u", id); + + g_dbus_emit_signal(connection, OPENOBEX_MANAGER_PATH, + OPENOBEX_MANAGER_INTERFACE, "TransferStarted", + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + g_free(path); +} + +void emit_transfer_completed(guint32 id, gboolean success) +{ + gchar *path = g_strdup_printf("/transfer%u", id); + + g_dbus_emit_signal(connection, OPENOBEX_MANAGER_PATH, + OPENOBEX_MANAGER_INTERFACE, "TransferCompleted", + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_BOOLEAN, &success, + DBUS_TYPE_INVALID); + + g_free(path); +} + +void emit_transfer_progress(guint32 id, guint32 total, guint32 transfered) +{ + gchar *path = g_strdup_printf("/transfer%u", id); + + g_dbus_emit_signal(connection, path, + TRANSFER_INTERFACE, "Progress", + DBUS_TYPE_INT32, &total, + DBUS_TYPE_INT32, &transfered, + DBUS_TYPE_INVALID); + + g_free(path); +} + +void register_transfer(guint32 id) +{ + gchar *path = g_strdup_printf("/transfer%u", id); + + if (!g_dbus_register_interface(connection, path, + TRANSFER_INTERFACE, + transfer_methods, transfer_signals, + NULL, NULL, NULL)) { + error("Cannot register Transfer interface."); + g_free(path); + return; + } + + g_free(path); +} + +void unregister_transfer(guint32 id) +{ + gchar *path = g_strdup_printf("/transfer%u", id); + + g_dbus_unregister_interface(connection, path, + TRANSFER_INTERFACE); + + g_free(path); +} + +static void agent_reply(DBusPendingCall *call, gpointer user_data) +{ + DBusMessage *reply = dbus_pending_call_steal_reply(call); + const gchar *name; + DBusError derr; + + agent->auth_pending = FALSE; + + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("Agent replied with an error: %s, %s", + derr.name, derr.message); + dbus_error_free(&derr); + return; + } + + if (dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + /* Splits folder and name */ + const gchar *slash = strrchr(name, '/'); + if (!slash) { + agent->new_name = g_strdup(name); + agent->new_folder = NULL; + } else { + agent->new_name = g_strdup(slash + 1); + agent->new_folder = g_strndup(name, slash - name); + } + } +} + +int request_authorization(gint32 cid, int fd, const gchar *filename, + const gchar *type, gint32 length, gint32 time, + gchar **new_folder, gchar **new_name) +{ + DBusMessage *msg; + DBusPendingCall *call; + struct sockaddr_rc addr; + socklen_t addrlen; + gchar address[18]; + const gchar *bda = address; + gchar *path; + + if (!agent) + return -1; + + if (agent->auth_pending) + return -EPERM; + + if (!new_folder || !new_name) + return -EINVAL; + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + if (getpeername(fd, (struct sockaddr *) &addr, &addrlen) < 0) + return -1; + + ba2str(&addr.rc_bdaddr, address); + + path = g_strdup_printf("/transfer%d", cid); + + msg = dbus_message_new_method_call(agent->bus_name, agent->path, + "org.openobex.Agent", "Authorize"); + + dbus_message_append_args(msg, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &bda, + DBUS_TYPE_STRING, &filename, + DBUS_TYPE_STRING, &type, + DBUS_TYPE_INT32, &length, + DBUS_TYPE_INT32, &time, + DBUS_TYPE_INVALID); + + g_free(path); + + if (!dbus_connection_send_with_reply(connection, + msg, &call, TIMEOUT)) + return -EPERM; + + dbus_message_unref(msg); + + agent->auth_pending = TRUE; + + dbus_pending_call_set_notify(call, agent_reply, NULL, NULL); + dbus_pending_call_unref(call); + + /* Workaround: process events while agent doesn't reply */ + while (agent->auth_pending) + g_main_context_iteration(NULL, TRUE); + + if (!agent->new_name) { + return -EPERM; + } + + *new_folder = agent->new_folder; + *new_name = agent->new_name; + agent->new_folder = NULL; + agent->new_name = NULL; + + return 0; +} diff --git a/obexd/src/obex.c b/obexd/src/obex.c new file mode 100644 index 000000000..4089b9f0b --- /dev/null +++ b/obexd/src/obex.c @@ -0,0 +1,765 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Nokia Corporation + * Copyright (C) 2007-2008 Instituto Nokia de Tecnologia (INdT) + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "logging.h" +#include "obex.h" +#include "dbus.h" + +/* Default MTU's */ +#define RX_MTU 32767 +#define TX_MTU 32767 + +#define TARGET_SIZE 16 +static const guint8 FTP_TARGET[TARGET_SIZE] = { 0xF9, 0xEC, 0x7B, 0xC4, + 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, + 0x00, 0xDC, 0x9E, 0x09 }; + +/* Connection ID */ +static guint32 cid = 0x0000; + +typedef struct { + guint8 version; + guint8 flags; + guint16 mtu; +} __attribute__ ((packed)) obex_connect_hdr_t; + +struct obex_commands opp = { + .get = opp_get, + .chkput = opp_chkput, + .put = opp_put, + .setpath = NULL, +}; + +struct obex_commands ftp = { + .get = ftp_get, + .put = ftp_put, + .setpath = ftp_setpath, +}; + +static void obex_session_free(struct obex_session *os) +{ + if (os->name) + g_free(os->name); + if (os->type) + g_free(os->type); + if (os->current_folder) + g_free(os->current_folder); + if (os->buf) + g_free(os->buf); + if (os->fd > 0) + close(os->fd); + g_free(os); +} + +static void cmd_connect(struct obex_session *os, + obex_t *obex, obex_object_t *obj) +{ + obex_connect_hdr_t *nonhdr; + obex_headerdata_t hd; + uint8_t *buffer; + guint hlen, newsize; + guint16 mtu; + guint8 hi; + + if (OBEX_ObjectGetNonHdrData(obj, &buffer) != sizeof(*nonhdr)) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + debug("Invalid OBEX CONNECT packet"); + return; + } + + nonhdr = (obex_connect_hdr_t *) buffer; + mtu = g_ntohs(nonhdr->mtu); + debug("Version: 0x%02x. Flags: 0x%02x OBEX packet length: %d", + nonhdr->version, nonhdr->flags, mtu); + /* Leave space for headers */ + newsize = mtu - 200; + + os->tx_mtu = newsize; + + debug("Resizing stream chunks to %d", newsize); + + /* connection id will be used to track the sessions, even for OPP */ + os->cid = ++cid; + + register_transfer(os->cid); + + if (os->target == NULL) { + /* OPP doesn't contains target or connection id. */ + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); + return; + } + + hi = hlen = 0; + OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen); + + if (hi != OBEX_HDR_TARGET || hlen != TARGET_SIZE + || memcmp(os->target, hd.bs, TARGET_SIZE) != 0) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + return; + } + + /* Append received UUID in WHO header */ + OBEX_ObjectAddHeader(obex, obj, + OBEX_HDR_WHO, hd, TARGET_SIZE, + OBEX_FL_FIT_ONE_PACKET); + hd.bq4 = cid; + OBEX_ObjectAddHeader(obex, obj, + OBEX_HDR_CONNECTION, hd, 4, + OBEX_FL_FIT_ONE_PACKET); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); + +} + +static gboolean chk_cid(obex_t *obex, obex_object_t *obj, guint32 cid) +{ + struct obex_session *os; + obex_headerdata_t hd; + guint hlen; + guint8 hi; + gboolean ret = FALSE; + + os = OBEX_GetUserData(obex); + + /* OPUSH doesn't provide a connection id. */ + if (os->target == NULL) + return TRUE; + + while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) { + if (hi == OBEX_HDR_CONNECTION && hlen == 4) { + ret = (hd.bq4 == cid ? TRUE : FALSE); + break; + } + } + + OBEX_ObjectReParseHeaders(obex, obj); + + if (ret == FALSE) + OBEX_ObjectSetRsp(obj, OBEX_RSP_SERVICE_UNAVAILABLE, + OBEX_RSP_SERVICE_UNAVAILABLE); + + return ret; +} + +static void cmd_get(struct obex_session *os, obex_t *obex, obex_object_t *obj) +{ + obex_headerdata_t hd; + guint hlen; + guint8 hi; + + if (!os->cmds->get) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED, + OBEX_RSP_NOT_IMPLEMENTED); + return; + } + + g_return_if_fail(chk_cid(obex, obj, os->cid)); + + if (os->type) { + g_free(os->type); + os->type = NULL; + } + + if (os->name) { + g_free(os->name); + os->name = NULL; + } + + if (os->buf) { + g_free(os->buf); + os->buf = NULL; + } + + while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) { + switch (hi) { + case OBEX_HDR_NAME: + if (os->name) { + debug("Ignoring multiple name headers"); + break; + } + + if (hlen == 0) + continue; + + os->name = g_convert((const gchar *) hd.bs, hlen, + "UTF8", "UTF16BE", NULL, NULL, NULL); + debug("OBEX_HDR_NAME: %s", os->name); + break; + case OBEX_HDR_TYPE: + if (os->type) { + debug("Ignoring multiple type headers"); + break; + } + + if (hlen == 0) + continue; + + /* Ensure null termination */ + if (hd.bs[hlen - 1] != '\0') + break; + + if (!g_utf8_validate((const gchar *) hd.bs, -1, NULL)) { + debug("Invalid type header: %s", hd.bs); + break; + } + + /* FIXME: x-obex/folder-listing - type is mandatory */ + + os->type = g_strndup((const gchar *) hd.bs, hlen); + debug("OBEX_HDR_TYPE: %s", os->type); + break; + } + } + + os->cmds->get(obex, obj); +} + +static void cmd_setpath(struct obex_session *os, + obex_t *obex, obex_object_t *obj) +{ + obex_headerdata_t hd; + guint32 hlen; + guint8 hi; + + if (!os->cmds->setpath) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED, + OBEX_RSP_NOT_IMPLEMENTED); + return; + } + + g_return_if_fail(chk_cid(obex, obj, os->cid)); + + if (os->name) { + g_free(os->name); + os->name = NULL; + } + + while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) { + if (hi == OBEX_HDR_NAME) { + if (os->name) { + debug("Ignoring multiple name headers"); + break; + } + + /* + * This is because OBEX_UnicodeToChar() accesses + * the string even if its size is zero + */ + if (hlen == 0) { + os->name = g_strdup(""); + break; + } + + os->name = g_convert((const gchar *) hd.bs, hlen, + "UTF8", "UTF16BE", NULL, NULL, NULL); + + debug("Set path name: %s", os->name); + break; + } + } + + if (!os->name) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_BAD_REQUEST, + OBEX_RSP_BAD_REQUEST); + return; + } + + os->cmds->setpath(obex, obj); +} + +gboolean os_prepare_get(struct obex_session *os, gchar *file, guint32 *size) +{ + gint fd; + struct stat stats; + + fd = open(file, O_RDONLY); + if (fd < 0) + goto fail; + + if (fstat(fd, &stats)) + goto fail; + + os->fd = fd; + os->offset = 0; + + if (stats.st_size > 0) + os->buf = g_new0(guint8, os->tx_mtu); + + *size = stats.st_size; + + return TRUE; + +fail: + if (fd >= 0) + close(fd); + + return FALSE; +} + +static gint obex_write(struct obex_session *os, + obex_t *obex, obex_object_t *obj) +{ + obex_headerdata_t hd; + gint32 len; + + debug("obex_write name: %s type: %s tx_mtu: %d fd: %d", + os->name, os->type, os->tx_mtu, os->fd); + + if (os->fd < 0) + return -EIO; + + len = read(os->fd, os->buf, os->tx_mtu); + if (len < 0) { + gint err = errno; + error("read(): %s (%d)", strerror(err), err); + g_free(os->buf); + return -err; + } + + os->offset += len; + + if (len == 0) { + OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_BODY, hd, 0, + OBEX_FL_STREAM_DATAEND); + g_free(os->buf); + os->buf = NULL; + return len; + } + + hd.bs = os->buf; + OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_BODY, hd, len, + OBEX_FL_STREAM_DATA); + + return len; +} + +static gint obex_read(struct obex_session *os, + obex_t *obex, obex_object_t *obj) +{ + gint size; + gint32 len = 0; + const guint8 *buffer; + + size = OBEX_ObjectReadStream(obex, obj, &buffer); + if (size < 0) { + error("Error on OBEX stream"); + return -EIO; + } + + if (size > os->rx_mtu) { + error("Received more data than RX_MAX"); + return -EIO; + } + + if (os->fd < 0 && size > 0) { + if (os->buf) { + error("Got more data but there is still a pending buffer"); + return -EIO; + } + + os->buf = g_malloc0(os->rx_mtu); + memcpy(os->buf, buffer, size); + os->offset = size; + + debug("Stored %u bytes into temporary buffer", size); + + return 0; + } + + while (len < size) { + gint w; + + w = write(os->fd, buffer + len, size - len); + if (w < 0) { + gint err = errno; + if (err == EINTR) + continue; + else + return -err; + } + + len += w; + } + + os->offset += len; + + return 0; +} + +static gboolean check_put(obex_t *obex, obex_object_t *obj) +{ + struct obex_session *os; + struct statvfs buf; + obex_headerdata_t hd; + guint hlen; + guint8 hi; + guint64 free; + + os = OBEX_GetUserData(obex); + + if (os->type) { + g_free(os->type); + os->type = NULL; + } + + if (os->name) { + g_free(os->name); + os->name = NULL; + } + + while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) { + switch (hi) { + case OBEX_HDR_NAME: + if (os->name) { + debug("Ignoring multiple name headers"); + break; + } + + if (hlen == 0) + continue; + + os->name = g_convert((const gchar *) hd.bs, hlen, + "UTF8", "UTF16BE", NULL, NULL, NULL); + debug("OBEX_HDR_NAME: %s", os->name); + break; + + case OBEX_HDR_TYPE: + if (os->type) { + debug("Ignoring multiple type headers"); + break; + } + + if (hlen == 0) + continue; + + /* Ensure null termination */ + if (hd.bs[hlen - 1] != '\0') + break; + + if (!g_utf8_validate((const gchar *) hd.bs, -1, NULL)) { + debug("Invalid type header: %s", hd.bs); + break; + } + + os->type = g_strndup((const gchar *) hd.bs, hlen); + debug("OBEX_HDR_TYPE: %s", os->type); + break; + + case OBEX_HDR_BODY: + os->size = -1; + break; + + case OBEX_HDR_LENGTH: + os->size = hd.bq4; + debug("OBEX_HDR_LENGTH: %d", os->size); + break; + } + } + + OBEX_ObjectReParseHeaders(obex, obj); + + if (!os->name) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_BAD_REQUEST, + OBEX_RSP_BAD_REQUEST); + g_free(os->type); + os->type = NULL; + return FALSE; + } + + if (!os->cmds->chkput) + goto done; + + if (os->cmds->chkput(obex, obj) < 0) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + return FALSE; + } + + if (fstatvfs(os->fd, &buf) < 0) { + int err = errno; + error("fstatvfs(): %s(%d)", strerror(err), err); + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + return FALSE; + } + + free = buf.f_bsize * buf.f_bavail; + debug("Free space in disk: %lu", free); + if (os->size > free) { + debug("Free disk space not available"); + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + return FALSE; + } + +done: + os->checked = TRUE; + + return TRUE; +} + +static void cmd_put(struct obex_session *os, obex_t *obex, obex_object_t *obj) +{ + if (!os->cmds->put) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED, + OBEX_RSP_NOT_IMPLEMENTED); + return; + } + + g_return_if_fail(chk_cid(obex, obj, os->cid)); + + if (!os->checked) { + if (!check_put(obex, obj)) + return; + } + + os->cmds->put(obex, obj); +} + +static void obex_event(obex_t *obex, obex_object_t *obj, gint mode, + gint evt, gint cmd, gint rsp) +{ + struct obex_session *os; + + obex_debug(evt, cmd, rsp); + + os = OBEX_GetUserData(obex); + + switch (evt) { + case OBEX_EV_PROGRESS: + emit_transfer_progress(os->cid, os->size, os->offset); + break; + case OBEX_EV_ABORT: + OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS); + break; + case OBEX_EV_REQDONE: + switch (cmd) { + case OBEX_CMD_DISCONNECT: + OBEX_TransportDisconnect(obex); + break; + case OBEX_CMD_PUT: + case OBEX_CMD_GET: + emit_transfer_completed(os->cid, + os->offset == os->size); + if (os->fd >= 0) { + close(os->fd); + os->fd = -1; + } + g_free(os->buf); + os->buf = NULL; + os->offset = 0; + break; + default: + break; + } + break; + case OBEX_EV_REQHINT: + switch (cmd) { + case OBEX_CMD_PUT: + os->checked = FALSE; + OBEX_ObjectReadStream(obex, obj, NULL); + case OBEX_CMD_GET: + case OBEX_CMD_SETPATH: + case OBEX_CMD_CONNECT: + case OBEX_CMD_DISCONNECT: + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, + OBEX_RSP_SUCCESS); + break; + default: + OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED, + OBEX_RSP_NOT_IMPLEMENTED); + break; + } + break; + case OBEX_EV_REQCHECK: + switch (cmd) { + case OBEX_CMD_PUT: + if (os->cmds->put) + check_put(obex, obj); + break; + default: + break; + } + break; + case OBEX_EV_REQ: + switch (cmd) { + case OBEX_CMD_DISCONNECT: + break; + case OBEX_CMD_CONNECT: + cmd_connect(os, obex, obj); + break; + case OBEX_CMD_SETPATH: + cmd_setpath(os, obex, obj); + break; + case OBEX_CMD_GET: + cmd_get(os, obex, obj); + break; + case OBEX_CMD_PUT: + cmd_put(os, obex, obj); + break; + default: + debug("Unknown request: 0x%X", cmd); + OBEX_ObjectSetRsp(obj, + OBEX_RSP_NOT_IMPLEMENTED, OBEX_RSP_NOT_IMPLEMENTED); + break; + } + break; + case OBEX_EV_STREAMAVAIL: + if (obex_read(os, obex, obj) < 0) { + debug("error obex_read()"); + OBEX_CancelRequest(obex, 1); + } + + break; + case OBEX_EV_STREAMEMPTY: + obex_write(os, obex, obj); + break; + case OBEX_EV_LINKERR: + break; + case OBEX_EV_PARSEERR: + break; + case OBEX_EV_UNEXPECTED: + break; + + default: + debug("Unknown evt %d", evt); + break; + } +} + +static void obex_handle_destroy(gpointer user_data) +{ + struct obex_session *os; + obex_t *obex = user_data; + + os = OBEX_GetUserData(obex); + + /* Got an error during a transfer. */ + if (os->fd >= 0) + emit_transfer_completed(os->cid, os->offset == os->size); + + unregister_transfer(os->cid); + + obex_session_free(os); + + OBEX_Cleanup(obex); +} + +static gboolean obex_handle_input(GIOChannel *io, + GIOCondition cond, gpointer user_data) +{ + obex_t *obex = user_data; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) + return FALSE; + + if (OBEX_HandleInput(obex, 1) < 0) { + error("Handle input error"); + return FALSE; + } + + return TRUE; +} + +gint obex_session_start(gint fd, struct server *server) +{ + struct obex_session *os; + GIOChannel *io; + obex_t *obex; + gint ret; + + os = g_new0(struct obex_session, 1); + switch (server->service) { + case OBEX_OPUSH: + os->target = NULL; + os->cmds = &opp; + break; + case OBEX_FTP: + os->target = FTP_TARGET; + os->cmds = &ftp; + break; + default: + g_free(os); + debug("Invalid OBEX server"); + return -EINVAL; + } + + os->current_folder = g_strdup(server->folder); + os->server = server; + os->rx_mtu = RX_MTU; + os->tx_mtu = TX_MTU; + os->fd = -1; + + obex = OBEX_Init(OBEX_TRANS_FD, obex_event, 0); + if (!obex) { + obex_session_free(os); + return -EIO; + } + + OBEX_SetUserData(obex, os); + + OBEX_SetTransportMTU(obex, os->rx_mtu, os->tx_mtu); + + ret = FdOBEX_TransportSetup(obex, fd, fd, 0); + if (ret < 0) { + obex_session_free(os); + OBEX_Cleanup(obex); + return ret; + } + + io = g_io_channel_unix_new(fd); + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + obex_handle_input, obex, obex_handle_destroy); + g_io_channel_unref(io); + + return 0; +} + +gint obex_session_stop() +{ + return 0; +} diff --git a/obexd/src/obex.h b/obexd/src/obex.h new file mode 100644 index 000000000..bd8868ff2 --- /dev/null +++ b/obexd/src/obex.h @@ -0,0 +1,76 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Nokia Corporation + * Copyright (C) 2007-2008 Instituto Nokia de Tecnologia (INdT) + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include + +#define OBEX_OPUSH 0x00 +#define OBEX_FTP 0x01 + +struct obex_commands { + void (*get) (obex_t *obex, obex_object_t *obj); + gint (*chkput) (obex_t *obex, obex_object_t *obj); + void (*put) (obex_t *obex, obex_object_t *obj); + void (*setpath) (obex_t *obex, obex_object_t *obj); +}; + +struct server { + guint16 service; + gboolean auto_accept; + gchar *folder; +}; + +struct obex_session { + guint32 cid; + guint16 tx_mtu; + guint16 rx_mtu; + gchar *name; + gchar *type; + gchar *current_folder; + guint8 *buf; + gint32 offset; + gint32 size; + gint fd; + const guint8 *target; + struct obex_commands *cmds; + struct server *server; + gboolean checked; +}; + +gint obex_session_start(gint fd, struct server *server); +gint obex_session_stop(); + +gint opp_chkput(obex_t *obex, obex_object_t *obj); +void opp_put(obex_t *obex, obex_object_t *obj); +void opp_get(obex_t *obex, obex_object_t *obj); + +void ftp_get(obex_t *obex, obex_object_t *obj); +void ftp_put(obex_t *obex, obex_object_t *obj); +void ftp_setpath(obex_t *obex, obex_object_t *obj); + +gboolean os_prepare_get(struct obex_session *os, gchar *file, guint32 *size); diff --git a/obexd/src/obexd.h b/obexd/src/obexd.h new file mode 100644 index 000000000..7baac9167 --- /dev/null +++ b/obexd/src/obexd.h @@ -0,0 +1,38 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 + +#define DBG(fmt, arg...) printf("%s: " fmt "\n" , __FUNCTION__ , ## arg) +//#define DBG(fmt, arg...) + +#include + +#define OPENOBEX_SERVICE "org.openobex" + +#define OPENOBEX_MANAGER_PATH "/" +#define OPENOBEX_MANAGER_INTERFACE OPENOBEX_SERVICE ".Manager" +#define ERROR_INTERFACE OPENOBEX_SERVICE ".Error" + +gboolean manager_init(DBusConnection *conn); +void manager_cleanup(void); diff --git a/obexd/src/opp.c b/obexd/src/opp.c new file mode 100644 index 000000000..70e6fe6ec --- /dev/null +++ b/obexd/src/opp.c @@ -0,0 +1,189 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2008 Nokia Corporation + * Copyright (C) 2007-2008 Instituto Nokia de Tecnologia (INdT) + * Copyright (C) 2007-2008 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "logging.h" +#include "dbus.h" +#include "obex.h" + +#define VCARD_TYPE "text/x-vcard" +#define VCARD_FILE CONFIGDIR "/vcard.vcf" + +gint opp_chkput(obex_t *obex, obex_object_t *obj) +{ + struct obex_session *os; + gchar *new_folder, *new_name, *path; + gint32 time, len = 0; + gint ret; + + os = OBEX_GetUserData(obex); + if (os == NULL) + return -EINVAL; + + if (!os->size) + return -EINVAL; + + if (os->server->auto_accept) + goto skip_auth; + + time = 0; + ret = request_authorization(os->cid, OBEX_GetFD(obex), + os->name ? os->name : "", + os->type ? os->type : "", + os->size, time, &new_folder, + &new_name); + + if (ret < 0) + return -EPERM; + + if (new_folder) { + g_free(os->current_folder); + os->current_folder = new_folder; + } + + if (new_name) { + g_free(os->name); + os->name = new_name; + } + +skip_auth: + path = g_build_filename(os->current_folder, os->name, NULL); + + os->fd = open(path, O_WRONLY | O_CREAT, 0600); + if (os->fd < 0) { + error("open(%s): %s (%d)", path, strerror(errno), errno); + g_free(path); + return -EPERM; + } + + g_free(path); + + emit_transfer_started(os->cid); + + if (!os->buf) { + debug("PUT request authorized, no buffered data"); + return 0; + } + + while (len < os->offset) { + gint w; + + w = write(os->fd, os->buf + len, os->offset - len); + if (w < 0) { + gint err = errno; + if (err == EINTR) + continue; + else + return -err; + } + + len += w; + } + + return 0; +} + +void opp_put(obex_t *obex, obex_object_t *obj) +{ + struct obex_session *os; + + os = OBEX_GetUserData(obex); + if (os == NULL) + return; + + if (os->current_folder == NULL) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + return; + } + + if (os->name == NULL) { + OBEX_ObjectSetRsp(obj, OBEX_RSP_BAD_REQUEST, OBEX_RSP_BAD_REQUEST); + return; + } + + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); +} + +void opp_get(obex_t *obex, obex_object_t *obj) +{ + struct obex_session *os; + obex_headerdata_t hv; + guint32 size; + + os = OBEX_GetUserData(obex); + if (os == NULL) + return; + + if (os->name) + goto fail; + + if (os->type == NULL) + goto fail; + + if (g_str_equal(os->type, VCARD_TYPE)) { + if (!os_prepare_get(os, VCARD_FILE, &size)) + goto fail; + } else + goto fail; + + + hv.bq4 = size; + OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_LENGTH, hv, 4, 0); + + /* Add body header */ + hv.bs = NULL; + if (size == 0) + OBEX_ObjectAddHeader (obex, obj, OBEX_HDR_BODY, + hv, 0, OBEX_FL_FIT_ONE_PACKET); + else + OBEX_ObjectAddHeader (obex, obj, OBEX_HDR_BODY, + hv, 0, OBEX_FL_STREAM_START); + + OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); + + return; + +fail: + OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN); + + return; +} -- 2.47.3