blob: dfcbab01d6fdc6922f114a9ed1bad8e921282b7c [file] [log] [blame]
/*
*
* OBEX Server
*
* Copyright (C) 2007-2010 Nokia Corporation
* Copyright (C) 2007-2010 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 <stdlib.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <fcntl.h>
#include <inttypes.h>
#include <glib.h>
#include <gobex/gobex.h>
#include "obexd.h"
#include "log.h"
#include "obex.h"
#include "obex-priv.h"
#include "server.h"
#include "manager.h"
#include "mimetype.h"
#include "service.h"
#include "transport.h"
#include "btio.h"
/* Challenge request */
#define NONCE_TAG 0x00
#define OPTIONS_TAG 0x01 /* Optional */
#define REALM_TAG 0x02 /* Optional */
#define NONCE_LEN 16
/* Challenge response */
#define DIGEST_TAG 0x00
#define USER_ID_TAG 0x01 /* Optional */
#define DIGEST_NONCE_TAG 0x02 /* Optional */
static GSList *sessions = NULL;
typedef struct {
uint8_t version;
uint8_t flags;
uint16_t mtu;
} __attribute__ ((packed)) obex_connect_hdr_t;
struct auth_header {
uint8_t tag;
uint8_t len;
uint8_t val[0];
} __attribute__ ((packed));
/* Possible commands */
static struct {
int cmd;
const char *name;
} obex_command[] = {
{ G_OBEX_OP_CONNECT, "CONNECT" },
{ G_OBEX_OP_DISCONNECT, "DISCONNECT" },
{ G_OBEX_OP_PUT, "PUT" },
{ G_OBEX_OP_GET, "GET" },
{ G_OBEX_OP_SETPATH, "SETPATH" },
{ G_OBEX_OP_SESSION, "SESSION" },
{ G_OBEX_OP_ABORT, "ABORT" },
{ G_OBEX_OP_ACTION, "ACTION" },
{ 0xFF, NULL },
};
/* Possible Response */
static struct {
int rsp;
const char *name;
} obex_response[] = {
{ G_OBEX_RSP_CONTINUE, "CONTINUE" },
{ G_OBEX_RSP_SUCCESS, "SUCCESS" },
{ G_OBEX_RSP_CREATED, "CREATED" },
{ G_OBEX_RSP_ACCEPTED, "ACCEPTED" },
{ G_OBEX_RSP_NON_AUTHORITATIVE, "NON_AUTHORITATIVE" },
{ G_OBEX_RSP_NO_CONTENT, "NO_CONTENT" },
{ G_OBEX_RSP_RESET_CONTENT, "RESET_CONTENT" },
{ G_OBEX_RSP_PARTIAL_CONTENT, "PARTIAL_CONTENT" },
{ G_OBEX_RSP_MULTIPLE_CHOICES, "MULTIPLE_CHOICES" },
{ G_OBEX_RSP_MOVED_PERMANENTLY, "MOVED_PERMANENTLY" },
{ G_OBEX_RSP_MOVED_TEMPORARILY, "MOVED_TEMPORARILY" },
{ G_OBEX_RSP_SEE_OTHER, "SEE_OTHER" },
{ G_OBEX_RSP_NOT_MODIFIED, "NOT_MODIFIED" },
{ G_OBEX_RSP_USE_PROXY, "USE_PROXY" },
{ G_OBEX_RSP_BAD_REQUEST, "BAD_REQUEST" },
{ G_OBEX_RSP_UNAUTHORIZED, "UNAUTHORIZED" },
{ G_OBEX_RSP_PAYMENT_REQUIRED, "PAYMENT_REQUIRED" },
{ G_OBEX_RSP_FORBIDDEN, "FORBIDDEN" },
{ G_OBEX_RSP_NOT_FOUND, "NOT_FOUND" },
{ G_OBEX_RSP_METHOD_NOT_ALLOWED, "METHOD_NOT_ALLOWED" },
{ G_OBEX_RSP_NOT_ACCEPTABLE, "NOT_ACCEPTABLE" },
{ G_OBEX_RSP_PROXY_AUTH_REQUIRED, "PROXY_AUTH_REQUIRED" },
{ G_OBEX_RSP_REQUEST_TIME_OUT, "REQUEST_TIME_OUT" },
{ G_OBEX_RSP_CONFLICT, "CONFLICT" },
{ G_OBEX_RSP_GONE, "GONE" },
{ G_OBEX_RSP_LENGTH_REQUIRED, "LENGTH_REQUIRED" },
{ G_OBEX_RSP_PRECONDITION_FAILED, "PRECONDITION_FAILED" },
{ G_OBEX_RSP_REQ_ENTITY_TOO_LARGE, "REQ_ENTITY_TOO_LARGE" },
{ G_OBEX_RSP_REQ_URL_TOO_LARGE, "REQ_URL_TOO_LARGE" },
{ G_OBEX_RSP_UNSUPPORTED_MEDIA_TYPE, "UNSUPPORTED_MEDIA_TYPE"},
{ G_OBEX_RSP_INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR" },
{ G_OBEX_RSP_NOT_IMPLEMENTED, "NOT_IMPLEMENTED" },
{ G_OBEX_RSP_BAD_GATEWAY, "BAD_GATEWAY" },
{ G_OBEX_RSP_SERVICE_UNAVAILABLE, "SERVICE_UNAVAILABLE" },
{ G_OBEX_RSP_GATEWAY_TIMEOUT, "GATEWAY_TIMEOUT" },
{ G_OBEX_RSP_VERSION_NOT_SUPPORTED, "VERSION_NOT_SUPPORTED" },
{ G_OBEX_RSP_DATABASE_FULL, "DATABASE_FULL" },
{ G_OBEX_RSP_DATABASE_LOCKED, "DATABASE_LOCKED" },
{ 0xFF, NULL },
};
static gboolean handle_async_io(void *object, int flags, int err,
void *user_data);
static void print_event(int cmd, int rsp)
{
const char *cmdstr = NULL, *rspstr = NULL;
int i;
static int lastcmd;
if (cmd < 0)
cmd = lastcmd;
else
lastcmd = cmd;
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;
}
obex_debug("%s(0x%x), %s(0x%x)", cmdstr, cmd, rspstr, rsp);
}
static void os_set_response(struct obex_session *os, int err)
{
uint8_t rsp;
rsp = g_obex_errno_to_rsp(err);
print_event(-1, rsp);
g_obex_send_rsp(os->obex, rsp, NULL, G_OBEX_HDR_INVALID);
}
static void os_session_mark_aborted(struct obex_session *os)
{
/* the session was already cancelled/aborted or size in unknown */
if (os->aborted || os->size == OBJECT_SIZE_UNKNOWN)
return;
os->aborted = (os->size != os->offset);
}
static void os_reset_session(struct obex_session *os)
{
os_session_mark_aborted(os);
if (os->object) {
os->driver->set_io_watch(os->object, NULL, NULL);
os->driver->close(os->object);
if (os->aborted && os->cmd == G_OBEX_OP_PUT && os->path &&
os->driver->remove)
os->driver->remove(os->path);
}
if (os->service && os->service->reset)
os->service->reset(os, os->service_data);
if (os->name) {
g_free(os->name);
os->name = NULL;
}
if (os->type) {
g_free(os->type);
os->type = NULL;
}
if (os->buf) {
g_free(os->buf);
os->buf = NULL;
}
if (os->path) {
g_free(os->path);
os->path = NULL;
}
if (os->apparam) {
g_free(os->apparam);
os->apparam = NULL;
os->apparam_len = 0;
}
if (os->get_rsp > 0) {
g_obex_remove_request_function(os->obex, os->get_rsp);
os->get_rsp = 0;
}
os->object = NULL;
os->driver = NULL;
os->aborted = FALSE;
os->pending = 0;
os->offset = 0;
os->size = OBJECT_SIZE_DELETE;
os->headers_sent = FALSE;
os->checked = FALSE;
}
static void obex_session_free(struct obex_session *os)
{
sessions = g_slist_remove(sessions, os);
if (os->io)
g_io_channel_unref(os->io);
if (os->obex)
g_obex_unref(os->obex);
g_free(os);
}
/* From Imendio's GnomeVFS OBEX module (om-utils.c) */
static time_t parse_iso8610(const char *val, int size)
{
time_t time, tz_offset = 0;
struct tm tm;
char *date;
char tz;
int nr;
memset(&tm, 0, sizeof(tm));
/* According to spec the time doesn't have to be null terminated */
date = g_strndup(val, size);
nr = sscanf(date, "%04u%02u%02uT%02u%02u%02u%c",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
&tz);
g_free(date);
if (nr < 6) {
/* Invalid time format */
return -1;
}
tm.tm_year -= 1900; /* Year since 1900 */
tm.tm_mon--; /* Months since January, values 0-11 */
tm.tm_isdst = -1; /* Daylight savings information not avail */
#if defined(HAVE_TM_GMTOFF)
tz_offset = tm.tm_gmtoff;
#elif defined(HAVE_TIMEZONE)
tz_offset = -timezone;
if (tm.tm_isdst > 0)
tz_offset += 3600;
#endif
time = mktime(&tm);
if (nr == 7) {
/*
* Date/Time was in localtime (to remote device)
* already. Since we don't know anything about the
* timezone on that one we won't try to apply UTC offset
*/
time += tz_offset;
}
return time;
}
static uint8_t *extract_nonce(const uint8_t *buffer, unsigned int hlen)
{
struct auth_header *hdr;
uint8_t *nonce = NULL;
uint32_t len = 0;
while (len < hlen) {
hdr = (void *) buffer + len;
switch (hdr->tag) {
case NONCE_TAG:
if (hdr->len != NONCE_LEN)
return NULL;
nonce = hdr->val;
break;
}
len += hdr->len + sizeof(struct auth_header);
}
return nonce;
}
static uint8_t *challenge_response(const uint8_t *nonce)
{
GChecksum *md5;
uint8_t *result;
size_t size;
result = g_new0(uint8_t, NONCE_LEN);
md5 = g_checksum_new(G_CHECKSUM_MD5);
if (md5 == NULL)
return result;
g_checksum_update(md5, nonce, NONCE_LEN);
g_checksum_update(md5, (uint8_t *) ":BlueZ", 6);
size = NONCE_LEN;
g_checksum_get_digest(md5, result, &size);
g_checksum_free(md5);
return result;
}
static void parse_service(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
const guint8 *target = NULL, *who = NULL;
gsize target_size = 0, who_size = 0;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_WHO);
if (hdr == NULL)
goto target;
g_obex_header_get_bytes(hdr, &who, &who_size);
target:
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TARGET);
if (hdr == NULL)
goto probe;
g_obex_header_get_bytes(hdr, &target, &target_size);
probe:
os->service = obex_service_driver_find(os->server->drivers,
target, target_size,
who, who_size);
}
static void parse_authchal(struct obex_session *session, GObexPacket *req,
GObexPacket *rsp)
{
GObexHeader *hdr;
const guint8 *data, *nonce = NULL;
gsize len;
uint8_t challenge[18];
struct auth_header *auth = (struct auth_header *) challenge;
uint8_t *response;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_AUTHCHAL);
if (hdr == NULL)
return;
if (!g_obex_header_get_bytes(hdr, &data, &len))
return;
nonce = extract_nonce(data, len);
DBG("AUTH CHALLENGE REQUEST");
response = challenge_response(nonce);
auth->tag = DIGEST_TAG;
auth->len = NONCE_LEN;
memcpy(auth->val, response, NONCE_LEN);
hdr = g_obex_header_new_bytes(G_OBEX_HDR_AUTHRESP, challenge,
sizeof(challenge));
g_obex_packet_add_header(rsp, hdr);
}
static void cmd_connect(GObex *obex, GObexPacket *req, void *user_data)
{
struct obex_session *os = user_data;
GObexPacket *rsp;
GObexHeader *hdr;
int err;
DBG("");
print_event(G_OBEX_OP_CONNECT, -1);
parse_service(os, req);
if (os->service == NULL || os->service->connect == NULL) {
error("Connect attempt to a non-supported target");
os_set_response(os, -EPERM);
return;
}
DBG("Selected driver: %s", os->service->name);
os->service_data = os->service->connect(os, &err);
if (err < 0) {
os_set_response(os, err);
return;
}
os->cmd = G_OBEX_OP_CONNECT;
rsp = g_obex_packet_new(G_OBEX_RSP_SUCCESS, TRUE, G_OBEX_HDR_INVALID);
parse_authchal(os, req, rsp);
if (os->service->target) {
hdr = g_obex_header_new_bytes(G_OBEX_HDR_WHO,
os->service->target,
os->service->target_size);
g_obex_packet_add_header(rsp, hdr);
}
g_obex_send(obex, rsp, NULL);
print_event(-1, 0);
}
static void cmd_disconnect(GObex *obex, GObexPacket *req, void *user_data)
{
struct obex_session *os = user_data;
DBG("session %p", os);
print_event(G_OBEX_OP_DISCONNECT, -1);
os->cmd = G_OBEX_OP_DISCONNECT;
os_set_response(os, 0);
}
static ssize_t driver_write(struct obex_session *os)
{
ssize_t len = 0;
while (os->pending > 0) {
ssize_t w;
w = os->driver->write(os->object, os->buf + len, os->pending);
if (w < 0) {
error("write(): %s (%zd)", strerror(-w), -w);
if (w == -EINTR)
continue;
else if (w == -EINVAL)
memmove(os->buf, os->buf + len, os->pending);
return w;
}
len += w;
os->offset += w;
os->pending -= w;
}
DBG("%zd written", len);
if (os->service->progress != NULL)
os->service->progress(os, os->service_data);
return len;
}
static gssize driver_read(struct obex_session *os, void *buf, gsize size)
{
gssize len;
if (os->object == NULL)
return -EIO;
if (os->service->progress != NULL)
os->service->progress(os, os->service_data);
len = os->driver->read(os->object, buf, size);
if (len < 0) {
error("read(): %s (%zd)", strerror(-len), -len);
if (len == -ENOSTR)
return 0;
if (len == -EAGAIN)
os->driver->set_io_watch(os->object, handle_async_io,
os);
}
os->offset += len;
DBG("%zd read", len);
return len;
}
static gssize send_data(void *buf, gsize size, gpointer user_data)
{
struct obex_session *os = user_data;
DBG("name=%s type=%s file=%p size=%zu", os->name, os->type, os->object,
size);
if (os->aborted)
return os->err < 0 ? os->err : -EPERM;
return driver_read(os, buf, size);
}
static void transfer_complete(GObex *obex, GError *err, gpointer user_data)
{
struct obex_session *os = user_data;
DBG("");
if (err != NULL) {
error("transfer failed: %s\n", err->message);
goto reset;
}
if (os->object && os->driver && os->driver->flush) {
if (os->driver->flush(os->object) == -EAGAIN) {
g_obex_suspend(os->obex);
os->driver->set_io_watch(os->object, handle_async_io,
os);
return;
}
}
reset:
os_reset_session(os);
}
static int driver_get_headers(struct obex_session *os)
{
GObexPacket *rsp;
gssize len;
guint8 data[255];
guint8 id;
GObexHeader *hdr;
DBG("name=%s type=%s object=%p", os->name, os->type, os->object);
if (os->aborted)
return os->err < 0 ? os->err : -EPERM;
if (os->object == NULL)
return -EIO;
if (os->headers_sent)
return 0;
rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, G_OBEX_HDR_INVALID);
if (os->driver->get_next_header == NULL)
goto done;
while ((len = os->driver->get_next_header(os->object, &data,
sizeof(data), &id))) {
if (len < 0) {
error("get_next_header(): %s (%zd)", strerror(-len),
-len);
g_obex_packet_free(rsp);
if (len == -EAGAIN)
return len;
g_free(os->buf);
os->buf = NULL;
return len;
}
hdr = g_obex_header_new_bytes(id, data, len);
g_obex_packet_add_header(rsp, hdr);
}
done:
if (os->size != OBJECT_SIZE_UNKNOWN && os->size < UINT32_MAX) {
hdr = g_obex_header_new_uint32(G_OBEX_HDR_LENGTH, os->size);
g_obex_packet_add_header(rsp, hdr);
}
g_obex_get_rsp_pkt(os->obex, rsp, send_data, transfer_complete, os,
NULL);
os->headers_sent = TRUE;
print_event(-1, G_OBEX_RSP_CONTINUE);
return 0;
}
static gboolean handle_async_io(void *object, int flags, int err,
void *user_data)
{
struct obex_session *os = user_data;
if (err < 0)
goto done;
if (flags & G_IO_OUT)
err = driver_write(os);
if ((flags & G_IO_IN) && !os->headers_sent)
err = driver_get_headers(os);
if (err == -EAGAIN)
return TRUE;
done:
if (err < 0) {
os->err = err;
os->aborted = TRUE;
}
g_obex_resume(os->obex);
return FALSE;
}
static gboolean recv_data(const void *buf, gsize size, gpointer user_data)
{
struct obex_session *os = user_data;
ssize_t ret;
DBG("name=%s type=%s file=%p size=%zu", os->name, os->type, os->object,
size);
if (os->aborted)
return FALSE;
/* workaround: client didn't send the object lenght */
if (os->size == OBJECT_SIZE_DELETE)
os->size = OBJECT_SIZE_UNKNOWN;
os->buf = g_realloc(os->buf, os->pending + size);
memcpy(os->buf + os->pending, buf, size);
os->pending += size;
/* only write if both object and driver are valid */
if (os->object == NULL || os->driver == NULL) {
DBG("Stored %" PRIu64 " bytes into temporary buffer",
os->pending);
return TRUE;
}
ret = driver_write(os);
if (ret >= 0)
return TRUE;
if (ret == -EAGAIN) {
g_obex_suspend(os->obex);
os->driver->set_io_watch(os->object, handle_async_io, os);
return TRUE;
}
return FALSE;
}
static void parse_type(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
const guint8 *type;
gsize len;
g_free(os->type);
os->type = NULL;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TYPE);
if (hdr == NULL)
goto probe;
if (!g_obex_header_get_bytes(hdr, &type, &len))
goto probe;
/* Ensure null termination */
if (type[len - 1] != '\0')
goto probe;
os->type = g_strndup((const char *) type, len);
DBG("TYPE: %s", os->type);
probe:
os->driver = obex_mime_type_driver_find(os->service->target,
os->service->target_size,
os->type,
os->service->who,
os->service->who_size);
}
static void parse_name(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
const char *name;
g_free(os->name);
os->name = NULL;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_NAME);
if (hdr == NULL)
return;
if (!g_obex_header_get_unicode(hdr, &name))
return;
os->name = g_strdup(name);
DBG("NAME: %s", os->name);
}
static void parse_apparam(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
const guint8 *apparam;
gsize len;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_APPARAM);
if (hdr == NULL)
return;
if (!g_obex_header_get_bytes(hdr, &apparam, &len))
return;
os->apparam = g_memdup(apparam, len);
os->apparam_len = len;
DBG("APPARAM");
}
static void cmd_get(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct obex_session *os = user_data;
int err;
DBG("session %p", os);
print_event(G_OBEX_OP_GET, -1);
if (os->service == NULL) {
os_set_response(os, -EPERM);
return;
}
if (os->service->get == NULL) {
os_set_response(os, -ENOSYS);
return;
}
os->headers_sent = FALSE;
if (os->type) {
g_free(os->type);
os->type = NULL;
}
parse_type(os, req);
if (!os->driver) {
error("No driver found");
os_set_response(os, -ENOSYS);
return;
}
os->cmd = G_OBEX_OP_GET;
parse_name(os, req);
parse_apparam(os, req);
err = os->service->get(os, os->service_data);
if (err == 0)
return;
os_set_response(os, err);
}
static void cmd_setpath(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct obex_session *os = user_data;
int err;
DBG("");
print_event(G_OBEX_OP_SETPATH, -1);
if (os->service == NULL) {
err = -EPERM;
goto done;
}
if (os->service->setpath == NULL) {
err = -ENOSYS;
goto done;
}
os->cmd = G_OBEX_OP_SETPATH;
parse_name(os, req);
os->nonhdr = g_obex_packet_get_data(req, &os->nonhdr_len);
err = os->service->setpath(os, os->service_data);
done:
os_set_response(os, err);
}
int obex_get_stream_start(struct obex_session *os, const char *filename)
{
int err;
void *object;
size_t size = OBJECT_SIZE_UNKNOWN;
object = os->driver->open(filename, O_RDONLY, 0, os->service_data,
&size, &err);
if (object == NULL) {
error("open(%s): %s (%d)", filename, strerror(-err), -err);
return err;
}
os->object = object;
os->offset = 0;
os->size = size;
err = driver_get_headers(os);
if (err != -EAGAIN)
return err;
g_obex_suspend(os->obex);
os->driver->set_io_watch(os->object, handle_async_io, os);
return 0;
}
int obex_put_stream_start(struct obex_session *os, const char *filename)
{
int err;
os->object = os->driver->open(filename, O_WRONLY | O_CREAT | O_TRUNC,
0600, os->service_data,
os->size != OBJECT_SIZE_UNKNOWN ?
(size_t *) &os->size : NULL, &err);
if (os->object == NULL) {
error("open(%s): %s (%d)", filename, strerror(-err), -err);
return err;
}
os->path = g_strdup(filename);
return 0;
}
static void parse_length(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
guint32 size;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_LENGTH);
if (hdr == NULL)
return;
if (!g_obex_header_get_uint32(hdr, &size))
return;
os->size = size;
DBG("LENGTH: %" PRIu64, os->size);
}
static void parse_time(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
const guint8 *time;
gsize len;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TIME);
if (hdr == NULL)
return;
if (!g_obex_header_get_bytes(hdr, &time, &len))
return;
os->time = parse_iso8610((const char *) time, len);
DBG("TIME: %s", ctime(&os->time));
}
static gboolean check_put(GObex *obex, GObexPacket *req, void *user_data)
{
struct obex_session *os = user_data;
int ret;
if (os->service->chkput == NULL)
goto done;
ret = os->service->chkput(os, os->service_data);
switch (ret) {
case 0:
break;
case -EAGAIN:
g_obex_suspend(os->obex);
os->driver->set_io_watch(os->object, handle_async_io, os);
return TRUE;
default:
os_set_response(os, ret);
return FALSE;
}
if (os->size == OBJECT_SIZE_DELETE || os->size == OBJECT_SIZE_UNKNOWN)
DBG("Got a PUT without a Length");
done:
os->checked = TRUE;
return TRUE;
}
static void cmd_put(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct obex_session *os = user_data;
int err;
DBG("");
print_event(G_OBEX_OP_PUT, -1);
if (os->service == NULL) {
os_set_response(os, -EPERM);
return;
}
parse_type(os, req);
if (os->driver == NULL) {
error("No driver found");
os_set_response(os, -ENOSYS);
return;
}
os->cmd = G_OBEX_OP_PUT;
parse_name(os, req);
parse_length(os, req);
parse_time(os, req);
parse_apparam(os, req);
if (!os->checked) {
if (!check_put(obex, req, user_data))
return;
}
if (os->service->put == NULL) {
os_set_response(os, -ENOSYS);
return;
}
err = os->service->put(os, os->service_data);
if (err == 0) {
g_obex_put_rsp(obex, req, recv_data, transfer_complete, os,
NULL, G_OBEX_HDR_INVALID);
print_event(G_OBEX_OP_PUT, G_OBEX_RSP_CONTINUE);
return;
}
os_set_response(os, err);
}
static void parse_destname(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
const char *destname;
g_free(os->destname);
os->destname = NULL;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_DESTNAME);
if (hdr == NULL)
return;
if (!g_obex_header_get_unicode(hdr, &destname))
return;
os->destname = g_strdup(destname);
DBG("DESTNAME: %s", os->destname);
}
static void parse_action(struct obex_session *os, GObexPacket *req)
{
GObexHeader *hdr;
guint8 id;
hdr = g_obex_packet_get_header(req, G_OBEX_HDR_ACTION);
if (hdr == NULL)
return;
if (!g_obex_header_get_uint8(hdr, &id))
return;
os->action_id = id;
DBG("ACTION: 0x%02x", os->action_id);
}
static void cmd_action(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct obex_session *os = user_data;
int err;
DBG("");
print_event(G_OBEX_OP_ACTION, -1);
if (os->service == NULL) {
err = -EPERM;
goto done;
}
if (os->service->action == NULL) {
err = -ENOSYS;
goto done;
}
os->cmd = G_OBEX_OP_ACTION;
parse_name(os, req);
parse_destname(os, req);
parse_action(os, req);
os->driver = obex_mime_type_driver_find(os->service->target,
os->service->target_size,
NULL,
os->service->who,
os->service->who_size);
if (os->driver == NULL) {
err = -ENOSYS;
goto done;
}
err = os->service->action(os, os->service_data);
done:
os_set_response(os, err);
}
static void cmd_abort(GObex *obex, GObexPacket *req, gpointer user_data)
{
struct obex_session *os = user_data;
DBG("");
print_event(G_OBEX_OP_ABORT, -1);
os_reset_session(os);
os_set_response(os, 0);
}
static void obex_session_destroy(struct obex_session *os)
{
DBG("");
os_reset_session(os);
if (os->service && os->service->disconnect)
os->service->disconnect(os, os->service_data);
obex_session_free(os);
}
static void disconn_func(GObex *obex, GError *err, gpointer user_data)
{
struct obex_session *os = user_data;
error("disconnected: %s\n", err ? err->message : "<no err>");
obex_session_destroy(os);
}
int obex_session_start(GIOChannel *io, uint16_t tx_mtu, uint16_t rx_mtu,
gboolean stream, struct obex_server *server)
{
struct obex_session *os;
GObex *obex;
GObexTransportType type;
static uint32_t id = 0;
DBG("");
os = g_new0(struct obex_session, 1);
os->id = ++id;
os->service = obex_service_driver_find(server->drivers, NULL,
0, NULL, 0);
os->server = server;
os->size = OBJECT_SIZE_DELETE;
type = stream ? G_OBEX_TRANSPORT_STREAM : G_OBEX_TRANSPORT_PACKET;
obex = g_obex_new(io, type, rx_mtu, tx_mtu);
if (!obex) {
obex_session_free(os);
return -EIO;
}
g_obex_set_disconnect_function(obex, disconn_func, os);
g_obex_add_request_function(obex, G_OBEX_OP_CONNECT, cmd_connect, os);
g_obex_add_request_function(obex, G_OBEX_OP_DISCONNECT, cmd_disconnect,
os);
g_obex_add_request_function(obex, G_OBEX_OP_PUT, cmd_put, os);
g_obex_add_request_function(obex, G_OBEX_OP_GET, cmd_get, os);
g_obex_add_request_function(obex, G_OBEX_OP_SETPATH, cmd_setpath, os);
g_obex_add_request_function(obex, G_OBEX_OP_ACTION, cmd_action, os);
g_obex_add_request_function(obex, G_OBEX_OP_ABORT, cmd_abort, os);
os->obex = obex;
os->io = g_io_channel_ref(io);
sessions = g_slist_prepend(sessions, os);
return 0;
}
const char *obex_get_name(struct obex_session *os)
{
return os->name;
}
const char *obex_get_destname(struct obex_session *os)
{
return os->destname;
}
void obex_set_name(struct obex_session *os, const char *name)
{
g_free(os->name);
os->name = g_strdup(name);
DBG("Name changed: %s", os->name);
}
ssize_t obex_get_size(struct obex_session *os)
{
return os->size;
}
const char *obex_get_type(struct obex_session *os)
{
return os->type;
}
int obex_remove(struct obex_session *os, const char *path)
{
if (os->driver == NULL)
return -ENOSYS;
return os->driver->remove(path);
}
int obex_copy(struct obex_session *os, const char *source,
const char *destination)
{
if (os->driver == NULL || os->driver->copy == NULL)
return -ENOSYS;
DBG("%s %s", source, destination);
return os->driver->copy(source, destination);
}
int obex_move(struct obex_session *os, const char *source,
const char *destination)
{
if (os->driver == NULL || os->driver->move == NULL)
return -ENOSYS;
DBG("%s %s", source, destination);
return os->driver->move(source, destination);
}
uint8_t obex_get_action_id(struct obex_session *os)
{
return os->action_id;
}
ssize_t obex_get_apparam(struct obex_session *os, const uint8_t **buffer)
{
*buffer = os->apparam;
return os->apparam_len;
}
ssize_t obex_get_non_header_data(struct obex_session *os,
const uint8_t **data)
{
*data = os->nonhdr;
return os->nonhdr_len;
}
int obex_getpeername(struct obex_session *os, char **name)
{
struct obex_transport_driver *transport = os->server->transport;
if (transport == NULL || transport->getpeername == NULL)
return -ENOTSUP;
return transport->getpeername(os->io, name);
}
int memncmp0(const void *a, size_t na, const void *b, size_t nb)
{
if (na != nb)
return na - nb;
if (a == NULL)
return -(a != b);
if (b == NULL)
return a != b;
return memcmp(a, b, na);
}