513 lines
11 KiB
C
513 lines
11 KiB
C
|
/*
|
||
|
*
|
||
|
* 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 <fcntl.h>
|
||
|
#include <stdio.h>
|
||
|
#include <errno.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <dirent.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <unistd.h>
|
||
|
#include <inttypes.h>
|
||
|
|
||
|
#include <glib.h>
|
||
|
#include <gdbus/gdbus.h>
|
||
|
|
||
|
#include "obexd.h"
|
||
|
#include "plugin.h"
|
||
|
#include "log.h"
|
||
|
#include "obex.h"
|
||
|
#include "mimetype.h"
|
||
|
#include "service.h"
|
||
|
#include "ftp.h"
|
||
|
|
||
|
#define PCSUITE_CHANNEL 24
|
||
|
#define PCSUITE_WHO_SIZE 8
|
||
|
|
||
|
#define PCSUITE_RECORD "<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \
|
||
|
<record> \
|
||
|
<attribute id=\"0x0001\"> \
|
||
|
<sequence> \
|
||
|
<uuid value=\"00005005-0000-1000-8000-0002ee000001\"/> \
|
||
|
</sequence> \
|
||
|
</attribute> \
|
||
|
\
|
||
|
<attribute id=\"0x0004\"> \
|
||
|
<sequence> \
|
||
|
<sequence> \
|
||
|
<uuid value=\"0x0100\"/> \
|
||
|
</sequence> \
|
||
|
<sequence> \
|
||
|
<uuid value=\"0x0003\"/> \
|
||
|
<uint8 value=\"%u\" name=\"channel\"/> \
|
||
|
</sequence> \
|
||
|
<sequence> \
|
||
|
<uuid value=\"0x0008\"/> \
|
||
|
</sequence> \
|
||
|
</sequence> \
|
||
|
</attribute> \
|
||
|
\
|
||
|
<attribute id=\"0x0005\"> \
|
||
|
<sequence> \
|
||
|
<uuid value=\"0x1002\"/> \
|
||
|
</sequence> \
|
||
|
</attribute> \
|
||
|
\
|
||
|
<attribute id=\"0x0009\"> \
|
||
|
<sequence> \
|
||
|
<sequence> \
|
||
|
<uuid value=\"00005005-0000-1000-8000-0002ee000001\"/> \
|
||
|
<uint16 value=\"0x0100\" name=\"version\"/> \
|
||
|
</sequence> \
|
||
|
</sequence> \
|
||
|
</attribute> \
|
||
|
\
|
||
|
<attribute id=\"0x0100\"> \
|
||
|
<text value=\"%s\" name=\"name\"/> \
|
||
|
</attribute> \
|
||
|
</record>"
|
||
|
|
||
|
#define BACKUP_BUS_NAME "com.nokia.backup.plugin"
|
||
|
#define BACKUP_PATH "/com/nokia/backup"
|
||
|
#define BACKUP_PLUGIN_INTERFACE "com.nokia.backup.plugin"
|
||
|
#define BACKUP_DBUS_TIMEOUT (1000 * 60 * 15)
|
||
|
|
||
|
static const uint8_t FTP_TARGET[TARGET_SIZE] = {
|
||
|
0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2,
|
||
|
0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 };
|
||
|
|
||
|
static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = {
|
||
|
'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' };
|
||
|
|
||
|
struct pcsuite_session {
|
||
|
struct ftp_session *ftp;
|
||
|
char *lock_file;
|
||
|
int fd;
|
||
|
};
|
||
|
|
||
|
static void *pcsuite_connect(struct obex_session *os, int *err)
|
||
|
{
|
||
|
struct pcsuite_session *pcsuite;
|
||
|
struct ftp_session *ftp;
|
||
|
int fd;
|
||
|
char *filename;
|
||
|
|
||
|
DBG("");
|
||
|
|
||
|
ftp = ftp_connect(os, err);
|
||
|
if (ftp == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
filename = g_build_filename(g_get_home_dir(), ".pcsuite", NULL);
|
||
|
|
||
|
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||
|
if (fd < 0 && errno != EEXIST) {
|
||
|
error("open(%s): %s(%d)", filename, strerror(errno), errno);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* Try to remove the file before retrying since it could be
|
||
|
that some process left/crash without removing it */
|
||
|
if (fd < 0) {
|
||
|
if (remove(filename) < 0) {
|
||
|
error("remove(%s): %s(%d)", filename, strerror(errno),
|
||
|
errno);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||
|
if (fd < 0) {
|
||
|
error("open(%s): %s(%d)", filename, strerror(errno),
|
||
|
errno);
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DBG("%s created", filename);
|
||
|
|
||
|
pcsuite = g_new0(struct pcsuite_session, 1);
|
||
|
pcsuite->ftp = ftp;
|
||
|
pcsuite->lock_file = filename;
|
||
|
pcsuite->fd = fd;
|
||
|
|
||
|
DBG("session %p created", pcsuite);
|
||
|
|
||
|
if (err)
|
||
|
*err = 0;
|
||
|
|
||
|
return pcsuite;
|
||
|
|
||
|
fail:
|
||
|
if (ftp)
|
||
|
ftp_disconnect(os, ftp);
|
||
|
if (err)
|
||
|
*err = -errno;
|
||
|
|
||
|
g_free(filename);
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int pcsuite_get(struct obex_session *os, void *user_data)
|
||
|
{
|
||
|
struct pcsuite_session *pcsuite = user_data;
|
||
|
|
||
|
DBG("%p", pcsuite);
|
||
|
|
||
|
return ftp_get(os, pcsuite->ftp);
|
||
|
}
|
||
|
|
||
|
static int pcsuite_chkput(struct obex_session *os, void *user_data)
|
||
|
{
|
||
|
struct pcsuite_session *pcsuite = user_data;
|
||
|
|
||
|
DBG("%p", pcsuite);
|
||
|
|
||
|
return ftp_chkput(os, pcsuite->ftp);
|
||
|
}
|
||
|
|
||
|
static int pcsuite_put(struct obex_session *os, void *user_data)
|
||
|
{
|
||
|
struct pcsuite_session *pcsuite = user_data;
|
||
|
|
||
|
DBG("%p", pcsuite);
|
||
|
|
||
|
return ftp_put(os, pcsuite->ftp);
|
||
|
}
|
||
|
|
||
|
static int pcsuite_setpath(struct obex_session *os, void *user_data)
|
||
|
{
|
||
|
struct pcsuite_session *pcsuite = user_data;
|
||
|
|
||
|
DBG("%p", pcsuite);
|
||
|
|
||
|
return ftp_setpath(os, pcsuite->ftp);
|
||
|
}
|
||
|
|
||
|
static int pcsuite_action(struct obex_session *os, void *user_data)
|
||
|
{
|
||
|
struct pcsuite_session *pcsuite = user_data;
|
||
|
|
||
|
DBG("%p", pcsuite);
|
||
|
|
||
|
return ftp_action(os, pcsuite->ftp);
|
||
|
}
|
||
|
|
||
|
static void pcsuite_disconnect(struct obex_session *os, void *user_data)
|
||
|
{
|
||
|
struct pcsuite_session *pcsuite = user_data;
|
||
|
|
||
|
DBG("%p", pcsuite);
|
||
|
|
||
|
if (pcsuite->fd >= 0)
|
||
|
close(pcsuite->fd);
|
||
|
|
||
|
if (pcsuite->lock_file) {
|
||
|
remove(pcsuite->lock_file);
|
||
|
g_free(pcsuite->lock_file);
|
||
|
}
|
||
|
|
||
|
if (pcsuite->ftp)
|
||
|
ftp_disconnect(os, pcsuite->ftp);
|
||
|
|
||
|
g_free(pcsuite);
|
||
|
}
|
||
|
|
||
|
static struct obex_service_driver pcsuite = {
|
||
|
.name = "Nokia OBEX PC Suite Services",
|
||
|
.service = OBEX_PCSUITE,
|
||
|
.channel = PCSUITE_CHANNEL,
|
||
|
.secure = TRUE,
|
||
|
.record = PCSUITE_RECORD,
|
||
|
.target = FTP_TARGET,
|
||
|
.target_size = TARGET_SIZE,
|
||
|
.who = PCSUITE_WHO,
|
||
|
.who_size = PCSUITE_WHO_SIZE,
|
||
|
.connect = pcsuite_connect,
|
||
|
.get = pcsuite_get,
|
||
|
.put = pcsuite_put,
|
||
|
.chkput = pcsuite_chkput,
|
||
|
.setpath = pcsuite_setpath,
|
||
|
.action = pcsuite_action,
|
||
|
.disconnect = pcsuite_disconnect
|
||
|
};
|
||
|
|
||
|
struct backup_object {
|
||
|
char *cmd;
|
||
|
int fd;
|
||
|
int oflag;
|
||
|
int error_code;
|
||
|
mode_t mode;
|
||
|
DBusPendingCall *pending_call;
|
||
|
DBusConnection *conn;
|
||
|
};
|
||
|
|
||
|
static void on_backup_dbus_notify(DBusPendingCall *pending_call,
|
||
|
void *user_data)
|
||
|
{
|
||
|
struct backup_object *obj = user_data;
|
||
|
DBusMessage *reply;
|
||
|
const char *filename;
|
||
|
int error_code;
|
||
|
|
||
|
DBG("Notification received for pending call - %s", obj->cmd);
|
||
|
|
||
|
reply = dbus_pending_call_steal_reply(pending_call);
|
||
|
|
||
|
if (reply && dbus_message_get_args(reply, NULL, DBUS_TYPE_INT32,
|
||
|
&error_code, DBUS_TYPE_STRING,
|
||
|
&filename, DBUS_TYPE_INVALID)) {
|
||
|
|
||
|
obj->error_code = error_code;
|
||
|
|
||
|
if (filename) {
|
||
|
DBG("Notification - file path = %s, error_code = %d",
|
||
|
filename, error_code);
|
||
|
if (error_code == 0)
|
||
|
obj->fd = open(filename,obj->oflag,obj->mode);
|
||
|
}
|
||
|
|
||
|
} else
|
||
|
DBG("Notification timed out or connection got closed");
|
||
|
|
||
|
if (reply)
|
||
|
dbus_message_unref(reply);
|
||
|
|
||
|
dbus_pending_call_unref(pending_call);
|
||
|
obj->pending_call = NULL;
|
||
|
dbus_connection_unref(obj->conn);
|
||
|
obj->conn = NULL;
|
||
|
|
||
|
if (obj->fd >= 0) {
|
||
|
DBG("File opened, setting io flags, cmd = %s",
|
||
|
obj->cmd);
|
||
|
if (obj->oflag == O_RDONLY)
|
||
|
obex_object_set_io_flags(user_data, G_IO_IN, 0);
|
||
|
else
|
||
|
obex_object_set_io_flags(user_data, G_IO_OUT, 0);
|
||
|
} else {
|
||
|
DBG("File open error, setting io error, cmd = %s",
|
||
|
obj->cmd);
|
||
|
obex_object_set_io_flags(user_data, G_IO_ERR, -EPERM);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean send_backup_dbus_message(const char *oper,
|
||
|
struct backup_object *obj,
|
||
|
size_t *size)
|
||
|
{
|
||
|
DBusConnection *conn;
|
||
|
DBusMessage *msg;
|
||
|
DBusPendingCall *pending_call;
|
||
|
gboolean ret = FALSE;
|
||
|
dbus_uint32_t file_size;
|
||
|
|
||
|
file_size = size ? *size : 0;
|
||
|
|
||
|
conn = g_dbus_setup_bus(DBUS_BUS_SESSION, NULL, NULL);
|
||
|
|
||
|
if (conn == NULL)
|
||
|
return FALSE;
|
||
|
|
||
|
msg = dbus_message_new_method_call(BACKUP_BUS_NAME, BACKUP_PATH,
|
||
|
BACKUP_PLUGIN_INTERFACE,
|
||
|
"request");
|
||
|
if (msg == NULL) {
|
||
|
dbus_connection_unref(conn);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
dbus_message_append_args(msg, DBUS_TYPE_STRING, &oper,
|
||
|
DBUS_TYPE_STRING, &obj->cmd,
|
||
|
DBUS_TYPE_INT32, &file_size,
|
||
|
DBUS_TYPE_INVALID);
|
||
|
|
||
|
if (strcmp(oper, "open") == 0) {
|
||
|
ret = g_dbus_send_message_with_reply(conn, msg, &pending_call,
|
||
|
BACKUP_DBUS_TIMEOUT);
|
||
|
dbus_message_unref(msg);
|
||
|
if (ret) {
|
||
|
obj->conn = conn;
|
||
|
obj->pending_call = pending_call;
|
||
|
ret = dbus_pending_call_set_notify(pending_call,
|
||
|
on_backup_dbus_notify,
|
||
|
obj, NULL);
|
||
|
} else
|
||
|
dbus_connection_unref(conn);
|
||
|
} else {
|
||
|
g_dbus_send_message(conn, msg);
|
||
|
dbus_connection_unref(conn);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void *backup_open(const char *name, int oflag, mode_t mode,
|
||
|
void *context, size_t *size, int *err)
|
||
|
{
|
||
|
struct backup_object *obj = g_new0(struct backup_object, 1);
|
||
|
|
||
|
DBG("cmd = %s", name);
|
||
|
|
||
|
obj->cmd = g_path_get_basename(name);
|
||
|
obj->oflag = oflag;
|
||
|
obj->mode = mode;
|
||
|
obj->fd = -1;
|
||
|
obj->pending_call = NULL;
|
||
|
obj->conn = NULL;
|
||
|
obj->error_code = 0;
|
||
|
|
||
|
if (send_backup_dbus_message("open", obj, size) == FALSE) {
|
||
|
g_free(obj);
|
||
|
obj = NULL;
|
||
|
}
|
||
|
|
||
|
if (err)
|
||
|
*err = 0;
|
||
|
|
||
|
return obj;
|
||
|
}
|
||
|
|
||
|
static int backup_close(void *object)
|
||
|
{
|
||
|
struct backup_object *obj = object;
|
||
|
size_t size = 0;
|
||
|
|
||
|
DBG("cmd = %s", obj->cmd);
|
||
|
|
||
|
if (obj->fd != -1)
|
||
|
close(obj->fd);
|
||
|
|
||
|
if (obj->pending_call) {
|
||
|
dbus_pending_call_cancel(obj->pending_call);
|
||
|
dbus_pending_call_unref(obj->pending_call);
|
||
|
dbus_connection_unref(obj->conn);
|
||
|
}
|
||
|
|
||
|
send_backup_dbus_message("close", obj, &size);
|
||
|
|
||
|
g_free(obj->cmd);
|
||
|
g_free(obj);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t backup_read(void *object, void *buf, size_t count)
|
||
|
{
|
||
|
struct backup_object *obj = object;
|
||
|
ssize_t ret = 0;
|
||
|
|
||
|
if (obj->pending_call) {
|
||
|
DBG("cmd = %s, IN WAITING STAGE", obj->cmd);
|
||
|
return -EAGAIN;
|
||
|
}
|
||
|
|
||
|
if (obj->fd != -1) {
|
||
|
DBG("cmd = %s, READING DATA", obj->cmd);
|
||
|
ret = read(obj->fd, buf, count);
|
||
|
if (ret < 0)
|
||
|
ret = -errno;
|
||
|
} else {
|
||
|
DBG("cmd = %s, PERMANENT FAILURE", obj->cmd);
|
||
|
ret = obj->error_code ? -obj->error_code : -ENOENT;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t backup_write(void *object, const void *buf, size_t count)
|
||
|
{
|
||
|
struct backup_object *obj = object;
|
||
|
ssize_t ret = 0;
|
||
|
|
||
|
if (obj->pending_call) {
|
||
|
DBG("cmd = %s, IN WAITING STAGE", obj->cmd);
|
||
|
return -EAGAIN;
|
||
|
}
|
||
|
|
||
|
if (obj->fd != -1) {
|
||
|
ret = write(obj->fd, buf, count);
|
||
|
|
||
|
DBG("cmd = %s, WRITTING", obj->cmd);
|
||
|
|
||
|
if (ret < 0) {
|
||
|
error("backup: cmd = %s", obj->cmd);
|
||
|
ret = -errno;
|
||
|
}
|
||
|
} else {
|
||
|
error("backup: cmd = %s", obj->cmd);
|
||
|
ret = obj->error_code ? -obj->error_code : -ENOENT;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int backup_flush(void *object)
|
||
|
{
|
||
|
DBG("%p", object);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct obex_mime_type_driver backup = {
|
||
|
.target = FTP_TARGET,
|
||
|
.target_size = TARGET_SIZE,
|
||
|
.mimetype = "application/vnd.nokia-backup",
|
||
|
.open = backup_open,
|
||
|
.close = backup_close,
|
||
|
.read = backup_read,
|
||
|
.write = backup_write,
|
||
|
.flush = backup_flush,
|
||
|
};
|
||
|
|
||
|
static int pcsuite_init(void)
|
||
|
{
|
||
|
int err;
|
||
|
|
||
|
err = obex_service_driver_register(&pcsuite);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
err = obex_mime_type_driver_register(&backup);
|
||
|
if (err < 0)
|
||
|
obex_service_driver_unregister(&pcsuite);
|
||
|
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static void pcsuite_exit(void)
|
||
|
{
|
||
|
obex_mime_type_driver_unregister(&backup);
|
||
|
obex_service_driver_unregister(&pcsuite);
|
||
|
}
|
||
|
|
||
|
OBEX_PLUGIN_DEFINE(pcsuite, pcsuite_init, pcsuite_exit)
|