Diff between a4d3df2ea53a2d9c5669a03708071a6fcbbeec9c and 67e2aa650792d03249063ce45bb12f2cab7e0cc2

Changed Files

File Additions Deletions Status
obexd/plugins/ebook.c +602 -140 modified
obexd/plugins/filesystem.c +2 -1 modified
obexd/plugins/ftp.c +135 -4 renamed
obexd/plugins/opp.c +78 -3 renamed
obexd/src/bluetooth.c +21 -14 modified
obexd/src/bluetooth.h +1 -2 modified
obexd/src/main.c +10 -11 modified
obexd/src/manager.c +34 -212 modified
obexd/src/obex.c +41 -88 modified
obexd/src/obex.h +6 -32 modified
obexd/src/pbap.c +0 -528 deleted
obexd/src/phonebook.c +0 -171 deleted
obexd/src/phonebook.h +0 -86 deleted
obexd/src/service.c +106 -0 added
obexd/src/service.h +40 -0 added

Full Patch

diff --git a/obexd/plugins/ebook.c b/obexd/plugins/ebook.c
index 9faecab..a5b43c8 100644
--- a/obexd/plugins/ebook.c
+++ b/obexd/plugins/ebook.c
@@ -27,13 +27,47 @@
 #endif
 
 #include <string.h>
+#include <errno.h>
+#include <glib.h>
+#include <bluetooth/bluetooth.h>
 
-#include <plugin.h>
-#include <logging.h>
-#include <phonebook.h>
+#include <openobex/obex.h>
+#include <openobex/obex_const.h>
 
 #include <libebook/e-book.h>
 
+#include "plugin.h"
+#include "logging.h"
+#include "obex.h"
+#include "service.h"
+
+#define PHONEBOOK_TYPE		"x-bt/phonebook"
+#define VCARDLISTING_TYPE	"x-bt/vcard-listing"
+#define VCARDENTRY_TYPE		"x-bt/vcard"
+
+#define ORDER_TAG		0x01
+#define SEARCHVALUE_TAG		0x02
+#define SEARCHATTRIB_TAG	0x03
+#define MAXLISTCOUNT_TAG	0x04
+#define LISTSTARTOFFSET_TAG	0x05
+#define FILTER_TAG		0x06
+#define FORMAT_TAG		0X07
+#define PHONEBOOKSIZE_TAG	0X08
+#define NEWMISSEDCALLS_TAG	0X09
+
+/* The following length is in the unit of byte */
+#define ORDER_LEN		1
+#define SEARCHATTRIB_LEN	1
+#define MAXLISTCOUNT_LEN	2
+#define LISTSTARTOFFSET_LEN	2
+#define FILTER_LEN		8
+#define FORMAT_LEN		1
+#define PHONEBOOKSIZE_LEN	2
+#define NEWMISSEDCALLS_LEN	1
+
+#define MCH		"telecom/mch.vcf"
+#define SIM1_MCH	"SIM1/telecom/mch.vcf"
+
 #define DEFAULT_COUNT 65535
 
 #define EOL_CHARS "\n"
@@ -47,15 +81,82 @@
 #define QUERY_GIVEN_NAME "(contains \"given_name\" \"%s\")"
 #define QUERY_PHONE "(contains \"phone\" \"%s\")"
 
+#define APPARAM_HDR_SIZE 2
+
+#define get_be64(val)	GUINT64_FROM_BE(bt_get_unaligned((guint64 *) val))
+#define get_be16(val)	GUINT16_FROM_BE(bt_get_unaligned((guint16 *) val))
+
+#define put_be16(val, ptr) bt_put_unaligned(GUINT16_TO_BE(val), (guint16 *) ptr)
+
+#define PBAP_CHANNEL	15
+
+#define PBAP_RECORD "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>	\
+<record>									\
+  <attribute id=\"0x0001\">							\
+    <sequence>									\
+      <uuid value=\"0x112f\"/>							\
+    </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=\"0x0009\">							\
+    <sequence>									\
+      <sequence>								\
+        <uuid value=\"0x1130\"/>						\
+        <uint16 value=\"0x0100\" name=\"version\"/>				\
+      </sequence>								\
+    </sequence>									\
+  </attribute>									\
+										\
+  <attribute id=\"0x0100\">							\
+    <text value=\"%s\" name=\"name\"/>						\
+  </attribute>									\
+										\
+  <attribute id=\"0x0314\">							\
+    <uint8 value=\"0x01\"/>							\
+  </attribute>									\
+</record>"
+
+struct apparam_hdr {
+	uint8_t		tag;
+	uint8_t		len;
+	uint8_t		val[0];
+} __attribute__ ((packed));
+
+struct apparam_field {
+	guint64		filter;
+	guint16		maxlistcount;
+	guint16		liststartoffset;
+	guint8		format;
+	guint8		order;
+	guint8		searchattrib;
+	guint8		*searchval;
+};
+
 struct phonebook_data {
-	struct phonebook_context *context;
-	guint64 filter;
-	guint8 format;
-	guint16 maxlistcount;
-	guint16 liststartoffset;
-	guint16 index;
+	obex_t *obex;
+	obex_object_t *obj;
+	struct apparam_field params;
 };
 
+static const guint8 PBAP_TARGET[TARGET_SIZE] = {
+			0x79, 0x61, 0x35, 0xF0,  0xF0, 0xC5, 0x11, 0xD8,
+			0x09, 0x66, 0x08, 0x00,  0x20, 0x0C, 0x9A, 0x66  };
+
 static char *vcard_attribs[29] = { EVC_VERSION, EVC_FN, EVC_N, EVC_PHOTO,
 				EVC_BDAY, EVC_ADR, EVC_LABEL, EVC_TEL,
 				EVC_EMAIL, EVC_MAILER, NULL, EVC_GEO,
@@ -64,37 +165,23 @@ static char *vcard_attribs[29] = { EVC_VERSION, EVC_FN, EVC_N, EVC_PHOTO,
 				EVC_UID, EVC_KEY, EVC_NICKNAME, EVC_CATEGORIES,
 				EVC_PRODID, NULL, NULL, NULL };
 
-static int ebook_create(struct phonebook_context *context)
-{
-	DBG("context %p", context);
-
-	return 0;
-}
-
-static void ebook_destroy(struct phonebook_context *context)
-{
-	DBG("context %p", context);
-}
-
 static void ebookpull_cb(EBook *book, EBookStatus status, GList *list,
 				gpointer user_data)
 {
 	struct phonebook_data *pb_data = user_data;
-	struct phonebook_context *context = pb_data->context;
-	guint64 filter = pb_data->filter;
-	guint8 format = pb_data->format;
-	guint16 liststartoffset = pb_data->liststartoffset, offset = 0;
-	guint16 maxlistcount = pb_data->maxlistcount, count = 0;
+	struct apparam_field *params = &pb_data->params;
+	struct obex_session *session = OBEX_GetUserData(pb_data->obex);
+	guint16 offset = 0, count = 0;
 	GList *contacts = list;
 	GString *pb;
 	gchar *result;
-	gint32 str_len;
+	gint32 size;
 
 	pb = g_string_new(NULL);
 
 	/* Mandatory attributes for vCard 3.0 are VERSION, N, FN and TEL */
-	if (filter != 0 && format == EVC_FORMAT_VCARD_30)
-		filter = filter | 0x87;
+	if (params->filter != 0 && params->format == EVC_FORMAT_VCARD_30)
+		params->filter |= 0x87;
 
 	for (; contacts != NULL; contacts = g_list_next(contacts)) {
 		EContact *contact = NULL;
@@ -102,12 +189,12 @@ static void ebookpull_cb(EBook *book, EBookStatus status, GList *list,
 		GList *attrib_list = NULL, *l;
 		char *vcard;
 
-		if (offset < liststartoffset) {
+		if (offset < params->liststartoffset) {
 			offset++;
 			continue;
 		}
 
-		if (count < maxlistcount)
+		if (count < params->maxlistcount)
 			count++;
 		else
 			break;
@@ -116,8 +203,8 @@ static void ebookpull_cb(EBook *book, EBookStatus status, GList *list,
 		evcard = E_VCARD(contact);
 		attrib_list = e_vcard_get_attributes(evcard);
 
-		if (!filter) {
-			vcard = e_vcard_to_string(evcard, format);
+		if (!params->filter) {
+			vcard = e_vcard_to_string(evcard, params->format);
 			goto done;
 		}
 
@@ -131,7 +218,7 @@ static void ebookpull_cb(EBook *book, EBookStatus status, GList *list,
 				int mask;
 
 				mask = 1 << i;
-				if (!(filter & mask))
+				if (!(params->filter & mask))
 					continue;
 				if (g_strcmp0(vcard_attribs[i], attrib_name))
 					continue;
@@ -142,7 +229,7 @@ static void ebookpull_cb(EBook *book, EBookStatus status, GList *list,
 				break;
 			}
 		}
-		vcard = e_vcard_to_string(evcard_filtered, format);
+		vcard = e_vcard_to_string(evcard_filtered, params->format);
 		g_object_unref(evcard_filtered);
 
 done:		g_string_append_printf(pb, "%s\n", vcard);
@@ -150,47 +237,38 @@ done:		g_string_append_printf(pb, "%s\n", vcard);
 	}
 
 	result = g_string_free(pb, FALSE);
-	str_len = strlen(result);
-	phonebook_return(context, result, str_len);
+	size = strlen(result);
+
+	if (size != 0) {
+		session->buf = g_realloc(session->buf, session->size + size);
+		memcpy(session->buf + session->size, result, size);
+		session->size += size;
+	}
 
-	if (str_len != 0)
-		phonebook_return(context, NULL, 0);
+	session->finished = 1;
+	OBEX_ResumeRequest(session->obex);
 
 	g_free(result);
 	g_free(pb_data);
-	phonebook_unref(context);
 	g_object_unref(book);
 }
 
-static int ebook_pullphonebook(struct phonebook_context *context,
-		gchar *objname, guint64 filter, guint8 format,
-		guint16 maxlistcount, guint16 liststartoffset,
-		guint16 *phonebooksize, guint8 *newmissedcalls)
+static int ebook_pullphonebook(obex_t *obex, obex_object_t *obj,
+				struct apparam_field params)
 {
 	struct phonebook_data *pb_data;
 	EBook *book;
 	EBookQuery *query;
 
-	DBG("context %p", context);
-
-	if (maxlistcount == 0) {
-		*phonebooksize = DEFAULT_COUNT;
-		return 0;
-	}
-
-	if (format != EVC_FORMAT_VCARD_30) {
+	if (params.format != EVC_FORMAT_VCARD_30) {
 		DBG("libebook does not support e_vcard_to_string_vcard_21()");
 		return -1;
 	}
 
-	phonebook_ref(context);
-
 	pb_data = g_new0(struct phonebook_data, 1);
-	pb_data->context = context;
-	pb_data->filter = filter;
-	pb_data->format = format;
-	pb_data->maxlistcount = maxlistcount;
-	pb_data->liststartoffset = liststartoffset;
+	pb_data->obex = obex;
+	pb_data->obj = obj;
+	pb_data->params = params;
 
 	book = e_book_new_default_addressbook(NULL);
 
@@ -202,6 +280,8 @@ static int ebook_pullphonebook(struct phonebook_context *context,
 
 	e_book_query_unref(query);
 
+	OBEX_SuspendRequest(obex, obj);
+
 	return 0;
 }
 
@@ -209,13 +289,13 @@ static void ebooklist_cb(EBook *book, EBookStatus status, GList *list,
 				gpointer user_data)
 {
 	struct phonebook_data *pb_data = user_data;
-	struct phonebook_context *context = pb_data->context;
-	guint16 liststartoffset = pb_data->liststartoffset, offset = 0;
-	guint16 maxlistcount = pb_data->maxlistcount, count = 0;
+	struct apparam_field *params = &pb_data->params;
+	struct obex_session *session = OBEX_GetUserData(pb_data->obex);
+	guint16 offset = 0, count = 0;
 	GString *listing;
 	GList *contacts = list;
 	gchar *result;
-	gint32 str_len;
+	gint32 size;
 
 	listing = g_string_new(VL_VERSION);
 	listing = g_string_append(listing, VL_TYPE);
@@ -228,12 +308,12 @@ static void ebooklist_cb(EBook *book, EBookStatus status, GList *list,
 		GList *name_values = NULL;
 		gchar *name = NULL, *name_part = NULL, *element = NULL;
 
-		if (offset < liststartoffset) {
+		if (offset < params->liststartoffset) {
 			offset++;
 			continue;
 		}
 
-		if (count < maxlistcount)
+		if (count < params->maxlistcount)
 			count++;
 		else
 			break;
@@ -267,23 +347,24 @@ static void ebooklist_cb(EBook *book, EBookStatus status, GList *list,
 
 	listing = g_string_append(listing, VL_BODY_END);
 	result = g_string_free(listing, FALSE);
-	str_len = strlen(result);
-	phonebook_return(context, result, str_len);
+	size = strlen(result);
+
+	if (size != 0) {
+		session->buf = g_realloc(session->buf, session->size + size);
+		memcpy(session->buf + session->size, result, size);
+		session->size += size;
+	}
 
-	if (str_len != 0)
-		phonebook_return(context, NULL, 0);
+	session->finished = 1;
+	OBEX_ResumeRequest(session->obex);
 
 	g_free(result);
 	g_free(pb_data);
-	phonebook_unref(context);
 	g_object_unref(book);
 }
 
-static int ebook_pullvcardlisting(struct phonebook_context *context,
-		gchar *objname, guint8 order, guint8 *searchval,
-		guint8 searchattrib, guint16 maxlistcount,
-		guint16 liststartoffset, guint16 *phonebooksize,
-		guint8 *newmissedcalls)
+static int ebook_pullvcardlisting(obex_t *obex, obex_object_t *obj,
+				struct apparam_field params)
 {
 	struct phonebook_data *pb_data;
 	EBook *book;
@@ -291,25 +372,10 @@ static int ebook_pullvcardlisting(struct phonebook_context *context,
 	gchar *str1 = NULL, *str2 = NULL;
 	gchar **value_list = NULL;
 
-	DBG("context %p", context);
-
-	if (maxlistcount == 0) {
-		*phonebooksize = DEFAULT_COUNT;
-		return 0;
-	}
-
-	/* libebook does not support sound attribute */
-	if (searchattrib >= 2) {
-		DBG("libebook does not support sound attribute");
-		return -1;
-	}
-
-	phonebook_ref(context);
-
 	pb_data = g_new0(struct phonebook_data, 1);
-	pb_data->context = context;
-	pb_data->maxlistcount = maxlistcount;
-	pb_data->liststartoffset = liststartoffset;
+	pb_data->obex = obex;
+	pb_data->obj = obj;
+	pb_data->params = params;
 
 	book = e_book_new_default_addressbook(NULL);
 
@@ -317,13 +383,13 @@ static int ebook_pullvcardlisting(struct phonebook_context *context,
 
 	/* All the vCards shall be returned if SearchValue header is
 	 * not specified */
-	if (!searchval || !strlen((char *)searchval)) {
+	if (!params.searchval || !strlen((char *) params.searchval)) {
 		query = e_book_query_any_field_contains("");
 		goto done;
 	}
 
-	if (searchattrib == 0) {
-		value_list = g_strsplit((gchar *)searchval, ";", 5);
+	if (params.searchattrib == 0) {
+		value_list = g_strsplit((gchar *) params.searchval, ";", 5);
 
 		if (value_list[0])
 			str1 = g_strdup_printf(QUERY_FAMILY_NAME,
@@ -340,8 +406,8 @@ static int ebook_pullvcardlisting(struct phonebook_context *context,
 		else
 			query = query1;
 	} else {
-		str1 = g_strdup_printf(QUERY_PHONE, searchval);
-		query = e_book_query_from_string((const char *)searchval);
+		str1 = g_strdup_printf(QUERY_PHONE, params.searchval);
+		query = e_book_query_from_string((char *) params.searchval);
 	}
 
 done:
@@ -356,6 +422,8 @@ done:
 	e_book_query_unref(query);
 	g_strfreev(value_list);
 
+	OBEX_SuspendRequest(obex, obj);
+
 	return 0;
 }
 
@@ -363,18 +431,19 @@ static void ebookpullentry_cb(EBook *book, EBookStatus status, GList *list,
                                 gpointer user_data)
 {
 	struct phonebook_data *pb_data = user_data;
-	struct phonebook_context *context = pb_data->context;
-	guint64 filter = pb_data->filter;
-	guint8 format = pb_data->format;
-	guint16 index = pb_data->index, i = 0;
+	struct apparam_field *params = &pb_data->params;
+	struct obex_session *session = OBEX_GetUserData(pb_data->obex);
+	guint16 i = 0, index;
 	GList *contacts = list, *attrib_list = NULL, *l;
 	EContact *contact = NULL;
 	EVCard *evcard = NULL, *evcard_filtered = NULL;
-	gint32 str_len = 0;
+	gint32 size = 0;
 	char *vcard = NULL;
 
-	if (filter != 0 && format == EVC_FORMAT_VCARD_30)
-		filter = filter | 0x87;
+	if (params->filter != 0 && params->format == EVC_FORMAT_VCARD_30)
+		params->filter |= 0x87;
+
+	sscanf(session->name, "%hu.vcf", &index);
 
 	for (; contacts != NULL; contacts = g_list_next(contacts)) {
 		if (i < index) {
@@ -385,8 +454,8 @@ static void ebookpullentry_cb(EBook *book, EBookStatus status, GList *list,
 		contact = E_CONTACT(contacts->data);
 		evcard = E_VCARD(contact);
 
-		if (!filter) {
-			vcard = e_vcard_to_string(evcard, format);
+		if (!params->filter) {
+			vcard = e_vcard_to_string(evcard, params->format);
 			break;
 		}
 
@@ -400,7 +469,7 @@ static void ebookpullentry_cb(EBook *book, EBookStatus status, GList *list,
 				int mask;
 
 				mask = 1 << i;
-				if (!(filter & mask))
+				if (!(params->filter & mask))
 					continue;
 				if (g_strcmp0(vcard_attribs[i], attrib_name))
 					continue;
@@ -412,50 +481,42 @@ static void ebookpullentry_cb(EBook *book, EBookStatus status, GList *list,
 				 break;
 			}
 		}
-		vcard = e_vcard_to_string(evcard_filtered, format);
+		vcard = e_vcard_to_string(evcard_filtered, params->format);
 		g_object_unref(evcard_filtered);
 		break;
 	}
 
-	if (vcard)
-		str_len = strlen(vcard);
-
-	phonebook_return(context, vcard, str_len);
+	if (vcard) {
+		size = strlen(vcard);
+		session->buf = g_realloc(session->buf, session->size + size);
+		memcpy(session->buf + session->size, vcard, size);
+		session->size += size;
+	}
 
-	if (str_len != 0)
-		phonebook_return(context, NULL, 0);
+	session->finished = 1;
+	OBEX_ResumeRequest(session->obex);
 
 	g_free(vcard);
 	g_free(pb_data);
-	phonebook_unref(context);
 	g_object_unref(book);
 }
 
-static int ebook_pullvcardentry(struct phonebook_context *context,
-		gchar *objname, guint64 filter, guint8 format)
+static int ebook_pullvcardentry(obex_t *obex, obex_object_t *obj,
+				struct apparam_field params)
 {
 	struct phonebook_data *pb_data;
 	EBook *book;
 	EBookQuery *query;
-	gint index;
-	gchar *ptr = NULL;
-
-	DBG("context %p", context);
 
-	if (format != EVC_FORMAT_VCARD_30) {
+	if (params.format != EVC_FORMAT_VCARD_30) {
 		DBG("libebook does not support e_vcard_to_string_vcard_21()");
 		return -1;
 	}
 
-	phonebook_ref(context);
-
-	ptr = g_strrstr(objname, "/");
-	sscanf(ptr, "/%d.vcf", &index);
 	pb_data = g_new0(struct phonebook_data, 1);
-	pb_data->context = context;
-	pb_data->filter = filter;
-	pb_data->format = format;
-	pb_data->index = index;
+	pb_data->obex = obex;
+	pb_data->obj = obj;
+	pb_data->params = params;
 
 	book = e_book_new_default_addressbook(NULL);
 
@@ -465,26 +526,427 @@ static int ebook_pullvcardentry(struct phonebook_context *context,
 
 	e_book_async_get_contacts(book, query, ebookpullentry_cb, pb_data);
 
+	OBEX_SuspendRequest(obex, obj);
+
 	return 0;
 }
 
-static struct phonebook_driver ebook_driver = {
-	.name		= "ebook",
-	.create		= ebook_create,
-	.destroy	= ebook_destroy,
-	.pullphonebook	= ebook_pullphonebook,
-	.pullvcardlisting = ebook_pullvcardlisting,
-	.pullvcardentry = ebook_pullvcardentry,
+static int pbap_parse_apparam_header(obex_t *obex, obex_object_t *obj,
+						struct apparam_field *apparam)
+{
+	obex_headerdata_t hd;
+	guint8 hi;
+	guint32 hlen;
+
+	while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) {
+		void *ptr = (void *) hd.bs;
+		uint32_t len = hlen;
+
+		if (hi != OBEX_HDR_APPARAM)
+			continue;
+
+		if (hlen < APPARAM_HDR_SIZE) {
+			g_free(apparam->searchval);
+			error("PBAP pullphonebook app parameters header"
+						" is too short: %d", hlen);
+			return -1;
+		}
+
+		while (len > APPARAM_HDR_SIZE) {
+			struct apparam_hdr *hdr = ptr;
+
+			if (hdr->len > len - APPARAM_HDR_SIZE) {
+				g_free(apparam->searchval);
+				error("Unexpected PBAP pullphonebook app"
+						" length, tag %d, len %d",
+							hdr->tag, hdr->len);
+				return -1;
+			}
+
+			switch (hdr->tag) {
+			case ORDER_TAG:
+				if (hdr->len == ORDER_LEN)
+					apparam->order = hdr->val[0];
+				break;
+			case SEARCHATTRIB_TAG:
+				if (hdr->len == SEARCHATTRIB_LEN)
+					apparam->searchattrib = hdr->val[0];
+				break;
+			case SEARCHVALUE_TAG:
+				apparam->searchval = g_try_malloc(hdr->len + 1);
+				if (apparam->searchval != NULL) {
+					memcpy(apparam->searchval, hdr->val,
+								hdr->len);
+					apparam->searchval[hdr->len] = '\0';
+				}
+				break;
+			case FILTER_TAG:
+				if (hdr->len == FILTER_LEN) {
+					guint64 val;
+					memcpy(&val, hdr->val, sizeof(val));
+					apparam->filter = get_be64(&val);
+				}
+				break;
+			case FORMAT_TAG:
+				if (hdr->len == FORMAT_LEN)
+					apparam->format = hdr->val[0];
+				break;
+			case MAXLISTCOUNT_TAG:
+				if (hdr->len == MAXLISTCOUNT_LEN) {
+					guint16 val;
+					memcpy(&val, hdr->val, sizeof(val));
+					apparam->maxlistcount = get_be16(&val);
+				}
+				break;
+			case LISTSTARTOFFSET_TAG:
+				if (hdr->len == LISTSTARTOFFSET_LEN) {
+					guint16 val;
+					memcpy(&val, hdr->val, sizeof(val));
+					apparam->liststartoffset = get_be16(&val);
+				}
+				break;
+			default:
+				g_free(apparam->searchval);
+				error("Unexpected PBAP pullphonebook app"
+						" parameter, tag %d, len %d",
+							hdr->tag, hdr->len);
+				return -1;
+			}
+
+			ptr += APPARAM_HDR_SIZE + hdr->len;
+			len -= APPARAM_HDR_SIZE + hdr->len;
+		}
+
+		/* Ignore multiple app param headers */
+		break;
+	}
+
+	return 0;
+}
+
+/* Add app parameter header, that is sent back to PBAP client */
+static int pbap_add_result_apparam_header(obex_t *obex, obex_object_t *obj,
+				guint16 maxlistcount, gchar *path_name,
+				guint16 phonebooksize,
+				guint8 newmissedcalls, gboolean *addbody)
+{
+	guint8 rspsize = 0;
+	gboolean addmissedcalls = FALSE;
+	obex_headerdata_t hd;
+
+	if (maxlistcount == 0) {
+		rspsize += APPARAM_HDR_SIZE + PHONEBOOKSIZE_LEN;
+		*addbody = FALSE;
+	}
+
+	if (g_str_equal(path_name, SIM1_MCH) == TRUE ||
+				g_str_equal(path_name, MCH) == TRUE) {
+		rspsize += APPARAM_HDR_SIZE + NEWMISSEDCALLS_LEN;
+		addmissedcalls = TRUE;
+	}
+
+	if (rspsize > 0) {
+		void *buf, *ptr;
+
+		buf = g_try_malloc0(rspsize);
+		if (buf == NULL)
+			return -ENOMEM;
+
+		ptr = buf;
+
+		if (maxlistcount == 0) {
+			struct apparam_hdr *hdr = ptr;
+			guint16 val = GUINT16_TO_BE(phonebooksize);
+
+			hdr->tag = PHONEBOOKSIZE_TAG;
+			hdr->len = PHONEBOOKSIZE_LEN;
+			memcpy(hdr->val, &val, sizeof(val));
+
+			ptr += APPARAM_HDR_SIZE + PHONEBOOKSIZE_LEN;
+		}
+
+		if (addmissedcalls == TRUE) {
+			struct apparam_hdr *hdr = ptr;
+
+			hdr->tag = NEWMISSEDCALLS_TAG;
+			hdr->len = NEWMISSEDCALLS_LEN;
+			hdr->val[0] = newmissedcalls;
+
+			ptr += APPARAM_HDR_SIZE + NEWMISSEDCALLS_LEN;
+		}
+
+		hd.bs = buf;
+		OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_APPARAM,
+							hd, rspsize, 0);
+		g_free(buf);
+	}
+
+	return 0;
+}
+
+static int pbap_pullphonebook(obex_t *obex, obex_object_t *obj,
+							gboolean *addbody)
+{
+	struct obex_session *session = OBEX_GetUserData(obex);
+	struct apparam_field params;
+	guint8 newmissedcalls = 0;
+	guint16 phonebooksize = 0;
+	int err;
+
+	memset(&params, 0, sizeof(struct apparam_field));
+
+	err = pbap_parse_apparam_header(obex, obj, &params);
+	if (err < 0)
+		return err;
+
+	if (params.maxlistcount == 0) {
+		phonebooksize = DEFAULT_COUNT;
+		goto done;
+	}
+
+	err = ebook_pullphonebook(obex, obj, params);
+	if (err < 0)
+		return err;
+
+done:
+	return pbap_add_result_apparam_header(obex, obj, params.maxlistcount,
+						session->name, phonebooksize,
+						newmissedcalls, addbody);
+}
+
+static int pbap_pullvcardlisting(obex_t *obex, obex_object_t *obj,
+							gboolean *addbody)
+{
+	struct obex_session *session = OBEX_GetUserData(obex);
+	gchar *fullname;
+	struct apparam_field params;
+	guint8 newmissedcalls = 0;
+	guint16 phonebooksize = 0;
+	int err;
+
+	memset(&params, 0, sizeof(struct apparam_field));
+
+	err = pbap_parse_apparam_header(obex, obj, &params);
+	if (err < 0)
+		return err;
+
+	if (params.maxlistcount == 0) {
+		phonebooksize = DEFAULT_COUNT;
+		goto proceed;
+	}
+
+	/* libebook does not support sound attribute */
+	if (params.searchattrib >= 2) {
+		DBG("libebook does not support sound attribute");
+		goto done;
+	}
+
+	err = ebook_pullvcardlisting(obex, obj, params);
+	if (err < 0)
+		goto done;
+
+proceed:
+
+	fullname = g_build_filename(session->current_folder, session->name,
+								NULL);
+	if (fullname != NULL)
+		fullname = g_strconcat(fullname, ".vcf", NULL);
+
+	err = pbap_add_result_apparam_header(obex, obj, params.maxlistcount,
+						fullname, phonebooksize,
+						newmissedcalls, addbody);
+	g_free(fullname);
+
+done:
+	g_free(params.searchval);
+	return err;
+}
+
+static int pbap_pullvcardentry(obex_t *obex, obex_object_t *obj)
+{
+	struct apparam_field params;
+	int err;
+
+	memset(&params, 0, sizeof(struct apparam_field));
+	err = pbap_parse_apparam_header(obex, obj, &params);
+	if (err < 0)
+		return err;
+
+	err = ebook_pullvcardentry(obex, obj, params);
+
+	g_free(params.searchval);
+	return err;
+}
+
+static void pbap_get(obex_t *obex, obex_object_t *obj)
+{
+	struct obex_session *session = OBEX_GetUserData(obex);
+	obex_headerdata_t hd;
+	gboolean addbody = TRUE;
+	int err;
+
+	if (session == NULL)
+		return;
+
+	if (session->type == NULL)
+		goto fail;
+
+	if (g_str_equal(session->type, VCARDLISTING_TYPE) == FALSE
+						&& session->name == NULL)
+		goto fail;
+
+	OBEX_ObjectReParseHeaders(obex, obj);
+
+	if (g_str_equal(session->type, PHONEBOOK_TYPE) == TRUE)
+		err = pbap_pullphonebook(obex, obj, &addbody);
+	else if (g_str_equal(session->type, VCARDLISTING_TYPE) == TRUE)
+		err = pbap_pullvcardlisting(obex, obj, &addbody);
+	else if (g_str_equal(session->type, VCARDENTRY_TYPE) == TRUE)
+		err = pbap_pullvcardentry(obex, obj);
+	else
+		goto fail;
+
+	if (err < 0)
+		goto fail;
+
+	if (addbody == TRUE) {
+		OBEX_SuspendRequest(obex, obj);
+		session->size = 0;
+
+		/* Add body header */
+		hd.bs = NULL;
+		OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_BODY,
+						hd, 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);
+}
+
+static gboolean pbap_is_valid_folder(struct obex_session *session)
+{
+	if (session->current_folder == NULL) {
+		if (g_str_equal(session->name, "telecom") == TRUE ||
+			g_str_equal(session->name, "SIM1") == TRUE)
+			return TRUE;
+	} else if (g_str_equal(session->current_folder, "SIM1") == TRUE) {
+		if (g_str_equal(session->name, "telecom") == TRUE)
+			return TRUE;
+	} else if (g_str_equal(session->current_folder, "telecom") == TRUE ||
+		g_str_equal(session->current_folder, "SIM1/telecom") == TRUE) {
+		if (g_str_equal(session->name, "pb") == TRUE ||
+				g_str_equal(session->name, "ich") == TRUE ||
+				g_str_equal(session->name, "och") == TRUE ||
+				g_str_equal(session->name, "mch") == TRUE ||
+				g_str_equal(session->name, "cch") == TRUE)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void pbap_setpath(obex_t *obex, obex_object_t *obj)
+{
+	struct obex_session *session = OBEX_GetUserData(obex);
+	guint8 *nonhdr;
+	gchar *fullname;
+
+	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 "Backup" flag */
+	if ((nonhdr[0] & 0x01) == 0x01) {
+		debug("Set to parent path");
+
+		if (session->current_folder == NULL) {
+			/* we are already in top level folder */
+			OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN,
+					OBEX_RSP_FORBIDDEN);
+			return;
+		}
+
+		fullname = g_path_get_dirname(session->current_folder);
+		g_free(session->current_folder);
+
+		if (strlen(fullname) == 1 && *fullname == '.')
+			session->current_folder = NULL;
+		else
+			session->current_folder = g_strdup(fullname);
+
+		g_free(fullname);
+
+		debug("Set to parent path: %s", session->current_folder);
+
+		OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
+		return;
+	}
+
+	if (!session->name) {
+		OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_BAD_REQUEST);
+		error("Set path failed: name missing!");
+		return;
+	}
+
+	if (strlen(session->name) == 0) {
+		debug("Set to root");
+
+		g_free(session->current_folder);
+		session->current_folder = NULL;
+
+		OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
+		return;
+	}
+
+	/* Check and set to name path */
+	if (strstr(session->name, "/")) {
+		OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
+		error("Set path failed: name incorrect!");
+		return;
+	}
+
+	if (pbap_is_valid_folder(session) == FALSE) {
+		OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_FOUND, OBEX_RSP_NOT_FOUND);
+		return;
+	}
+
+	if (session->current_folder == NULL)
+		fullname = g_build_filename("", session->name, NULL);
+	else
+		fullname = g_build_filename(session->current_folder, session->name, NULL);
+
+	debug("Fullname: %s", fullname);
+
+	g_free(session->current_folder);
+	session->current_folder = fullname;
+	OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
+}
+
+struct obex_service_driver driver = {
+	.name = "Phonebook Access server",
+	.service = OBEX_PBAP,
+	.channel = PBAP_CHANNEL,
+	.record = PBAP_RECORD,
+	.target = PBAP_TARGET,
+	.get = pbap_get,
+	.setpath = pbap_setpath
 };
 
 static int ebook_init(void)
 {
-	return phonebook_driver_register(&ebook_driver);
+	return obex_service_driver_register(&driver);
 }
 
 static void ebook_exit(void)
 {
-	phonebook_driver_unregister(&ebook_driver);
+	obex_service_driver_unregister(&driver);
 }
 
 OBEX_PLUGIN_DEFINE("ebook", ebook_init, ebook_exit)
diff --git a/obexd/plugins/filesystem.c b/obexd/plugins/filesystem.c
index e275bf2..4a06c98 100644
--- a/obexd/plugins/filesystem.c
+++ b/obexd/plugins/filesystem.c
@@ -48,6 +48,7 @@
 #include "logging.h"
 #include "mimetype.h"
 #include "obex.h"
+#include "service.h"
 
 #define EOL_CHARS "\n"
 
@@ -266,7 +267,7 @@ static ssize_t folder_read(gpointer object, void *buf, size_t count)
 	if (os->finished)
 		return 0;
 
-	pcsuite = os->server->services & OBEX_PCSUITE ? TRUE : FALSE;
+	pcsuite = os->service->service & OBEX_PCSUITE ? TRUE : FALSE;
 
 	listing = g_string_new(FL_VERSION);
 	listing = g_string_append(listing, pcsuite ? FL_TYPE_PCSUITE : FL_TYPE);
diff --git a/obexd/src/ftp.c b/obexd/plugins/ftp.c
similarity index 56%
rename from obexd/src/ftp.c
rename to obexd/plugins/ftp.c
index ffbcf73..82f16f2 100644
--- a/obexd/src/ftp.c
+++ b/obexd/plugins/ftp.c
@@ -45,14 +45,102 @@
 #include <openobex/obex.h>
 #include <openobex/obex_const.h>
 
+#include "plugin.h"
 #include "logging.h"
 #include "obex.h"
 #include "dbus.h"
 #include "mimetype.h"
+#include "service.h"
 
 #define LST_TYPE "x-obex/folder-listing"
 #define CAP_TYPE "x-obex/capability"
 
+#define FTP_CHANNEL    10
+#define FTP_RECORD "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>        \
+<record>                                                                       \
+  <attribute id=\"0x0001\">                                                    \
+    <sequence>                                                                 \
+      <uuid value=\"0x1106\"/>                                                 \
+    </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=\"0x0009\">                                                    \
+    <sequence>                                                                 \
+      <sequence>                                                               \
+        <uuid value=\"0x1106\"/>                                               \
+        <uint16 value=\"0x0100\" name=\"version\"/>                            \
+      </sequence>                                                              \
+    </sequence>                                                                        \
+  </attribute>                                                                 \
+                                                                               \
+  <attribute id=\"0x0100\">                                                    \
+    <text value=\"%s\" name=\"name\"/>                                         \
+  </attribute>                                                                 \
+</record>"
+
+#define PCSUITE_CHANNEL        24
+#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>"
+
+static const guint8 FTP_TARGET[TARGET_SIZE] = {
+			0xF9, 0xEC, 0x7B, 0xC4,  0x95, 0x3C, 0x11, 0xD2,
+			0x98, 0x4E, 0x52, 0x54,  0x00, 0xDC, 0x9E, 0x09  };
+
 static gint folder_listing(struct obex_session *os, size_t *size)
 {
 	return os_prepare_get(os, os->current_folder, size);
@@ -101,7 +189,7 @@ static gint ftp_prepare_get(struct obex_session *os, gchar *file,
 	return os_prepare_get(os, file, size);
 }
 
-void ftp_get(obex_t *obex, obex_object_t *obj)
+static void ftp_get(obex_t *obex, obex_object_t *obj)
 {
 	obex_headerdata_t hv;
 	struct obex_session *os;
@@ -179,7 +267,7 @@ static gint ftp_delete(struct obex_session *os)
 	return ret;
 }
 
-gint ftp_chkput(obex_t *obex, obex_object_t *obj)
+static gint ftp_chkput(obex_t *obex, obex_object_t *obj)
 {
 	struct obex_session *os;
 
@@ -193,7 +281,7 @@ gint ftp_chkput(obex_t *obex, obex_object_t *obj)
 	return os_prepare_put(os);
 }
 
-void ftp_put(obex_t *obex, obex_object_t *obj)
+static void ftp_put(obex_t *obex, obex_object_t *obj)
 {
 	struct obex_session *os;
 	int ret = 0;
@@ -232,7 +320,7 @@ void ftp_put(obex_t *obex, obex_object_t *obj)
 	}
 }
 
-void ftp_setpath(obex_t *obex, obex_object_t *obj)
+static void ftp_setpath(obex_t *obex, obex_object_t *obj)
 {
 	struct obex_session *os;
 	guint8 *nonhdr;
@@ -343,3 +431,46 @@ not_found:
 done:
 	g_free(fullname);
 }
+
+struct obex_service_driver pcsuite = {
+	.name = "Nokia OBEX PC Suite Services",
+	.service = OBEX_PCSUITE,
+	.channel = PCSUITE_CHANNEL,
+	.record = PCSUITE_RECORD,
+	.target = FTP_TARGET,
+	.get = ftp_get,
+	.put = ftp_put,
+	.chkput = ftp_chkput,
+	.setpath = ftp_setpath
+};
+
+struct obex_service_driver ftp = {
+	.name = "File Transfer server",
+	.service = OBEX_FTP,
+	.channel = FTP_CHANNEL,
+	.record = FTP_RECORD,
+	.target = FTP_TARGET,
+	.get = ftp_get,
+	.put = ftp_put,
+	.chkput = ftp_chkput,
+	.setpath = ftp_setpath
+};
+
+static int ftp_init(void)
+{
+	int err;
+
+	err = obex_service_driver_register(&ftp);
+	if (err < 0)
+		return err;
+
+	return obex_service_driver_register(&pcsuite);
+}
+
+static void ftp_exit(void)
+{
+	obex_service_driver_unregister(&ftp);
+	obex_service_driver_unregister(&pcsuite);
+}
+
+OBEX_PLUGIN_DEFINE("ftp", ftp_init, ftp_exit)
diff --git a/obexd/src/opp.c b/obexd/plugins/opp.c
similarity index 63%
rename from obexd/src/opp.c
rename to obexd/plugins/opp.c
index a8dce13..7d5a735 100644
--- a/obexd/src/opp.c
+++ b/obexd/plugins/opp.c
@@ -34,6 +34,8 @@
 
 #include <glib.h>
 
+#include "plugin.h"
+#include "service.h"
 #include "logging.h"
 #include "obex.h"
 #include "dbus.h"
@@ -41,7 +43,58 @@
 #define VCARD_TYPE "text/x-vcard"
 #define VCARD_FILE CONFIGDIR "/vcard.vcf"
 
-gint opp_chkput(obex_t *obex, obex_object_t *obj)
+#define OPP_CHANNEL	9
+#define OPP_RECORD "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>	\
+<record>									\
+  <attribute id=\"0x0001\">							\
+    <sequence>									\
+      <uuid value=\"0x1105\"/>							\
+    </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=\"0x0009\">							\
+    <sequence>									\
+      <sequence>								\
+        <uuid value=\"0x1105\"/>						\
+        <uint16 value=\"0x0100\" name=\"version\"/>				\
+      </sequence>								\
+    </sequence>									\
+  </attribute>									\
+										\
+  <attribute id=\"0x0100\">							\
+    <text value=\"%s\" name=\"name\"/>						\
+  </attribute>									\
+										\
+  <attribute id=\"0x0303\">							\
+    <sequence>									\
+      <uint8 value=\"0x01\"/>							\
+      <uint8 value=\"0x01\"/>							\
+      <uint8 value=\"0x02\"/>							\
+      <uint8 value=\"0x03\"/>							\
+      <uint8 value=\"0x04\"/>							\
+      <uint8 value=\"0x05\"/>							\
+      <uint8 value=\"0x06\"/>							\
+      <uint8 value=\"0xff\"/>							\
+    </sequence>									\
+  </attribute>									\
+</record>"
+
+static gint opp_chkput(obex_t *obex, obex_object_t *obj)
 {
 	struct obex_session *os;
 	gchar *new_folder, *new_name;
@@ -82,7 +135,7 @@ skip_auth:
 	return os_prepare_put(os);
 }
 
-void opp_put(obex_t *obex, obex_object_t *obj)
+static void opp_put(obex_t *obex, obex_object_t *obj)
 {
 	struct obex_session *os;
 
@@ -103,7 +156,7 @@ void opp_put(obex_t *obex, obex_object_t *obj)
 	OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
 }
 
-void opp_get(obex_t *obex, obex_object_t *obj)
+static void opp_get(obex_t *obex, obex_object_t *obj)
 {
 	struct obex_session *os;
 	obex_headerdata_t hv;
@@ -145,3 +198,25 @@ void opp_get(obex_t *obex, obex_object_t *obj)
 fail:
 	OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
 }
+
+struct obex_service_driver driver = {
+	.name = "Object Push server",
+	.service = OBEX_OPP,
+	.channel = OPP_CHANNEL,
+	.record = OPP_RECORD,
+	.get = opp_get,
+	.put = opp_put,
+	.chkput = opp_chkput,
+};
+
+static int opp_init(void)
+{
+	return obex_service_driver_register(&driver);
+}
+
+static void opp_exit(void)
+{
+	obex_service_driver_unregister(&driver);
+}
+
+OBEX_PLUGIN_DEFINE("opp", opp_init, opp_exit)
diff --git a/obexd/src/bluetooth.c b/obexd/src/bluetooth.c
index 9cf2acf..7cbfda1 100644
--- a/obexd/src/bluetooth.c
+++ b/obexd/src/bluetooth.c
@@ -48,6 +48,7 @@
 #include "obex.h"
 #include "dbus.h"
 #include "btio.h"
+#include "service.h"
 
 #define BT_RX_MTU 32767
 #define BT_TX_MTU 32767
@@ -57,6 +58,7 @@ static GSList *servers = NULL;
 static void confirm_event(GIOChannel *io, gpointer user_data)
 {
 	struct server *server = user_data;
+	struct obex_service_driver *driver;
 	GError *err = NULL;
 	char address[18];
 	guint8 channel;
@@ -73,7 +75,9 @@ static void confirm_event(GIOChannel *io, gpointer user_data)
 
 	info("New connection from: %s, channel %u", address, channel);
 
-	if (server->services != OBEX_OPP) {
+	driver = (struct obex_service_driver *) server->drivers->data;
+
+	if (driver->service != OBEX_OPP) {
 		if (request_service_authorization(server, io, address) < 0)
 			goto drop;
 
@@ -95,18 +99,21 @@ drop:
 static gint server_start(struct server *server)
 {
 	GError *err = NULL;
+	struct obex_service_driver *driver;
+
+	driver = (struct obex_service_driver *) server->drivers->data;
 
 	/* Listen */
 	if (server->secure)
 		server->io = bt_io_listen(BT_IO_RFCOMM, NULL, confirm_event,
 					server, NULL, &err,
-					BT_IO_OPT_CHANNEL, server->channel,
+					BT_IO_OPT_CHANNEL, driver->channel,
 					BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
 					BT_IO_OPT_INVALID);
 	else
 		server->io = bt_io_listen(BT_IO_RFCOMM, NULL, confirm_event,
 					server, NULL, &err,
-					BT_IO_OPT_CHANNEL, server->channel,
+					BT_IO_OPT_CHANNEL, driver->channel,
 					BT_IO_OPT_INVALID);
 	if (!server->io)
 		goto failed;
@@ -137,21 +144,23 @@ static gint server_stop(struct server *server)
 	return 0;
 }
 
-static gint server_register(guint16 service, const gchar *name, guint8 channel,
-			const gchar *folder, gboolean secure,
-			gboolean auto_accept, gboolean symlinks,
-			const gchar *capability)
+static gint server_register(guint16 service, const gchar *folder,
+				gboolean secure, gboolean auto_accept,
+				gboolean symlinks, const gchar *capability)
 {
 	struct server *server;
+	GSList *drivers;
+
+	drivers = obex_service_driver_list(service);
+	if (drivers == NULL)
+		return -EINVAL;
 
 	server = g_new0(struct server, 1);
-	server->services = service;
-	server->name = g_strdup(name);
+	server->drivers = drivers;
 	server->folder = g_strdup(folder);
 	server->auto_accept = auto_accept;
 	server->symlinks = symlinks;
 	server->capability = g_strdup(capability);
-	server->channel = channel;
 	server->secure = secure;
 	server->rx_mtu = BT_RX_MTU;
 	server->tx_mtu = BT_TX_MTU;
@@ -161,13 +170,11 @@ static gint server_register(guint16 service, const gchar *name, guint8 channel,
 	return 0;
 }
 
-gint bluetooth_init(guint service, const gchar *name, const gchar *folder,
-				guint8 channel, gboolean secure,
+gint bluetooth_init(guint service, const gchar *folder, gboolean secure,
 				gboolean auto_accept, gboolean symlinks,
 				const gchar *capability)
 {
-	return server_register(service, name, channel, folder,
-					secure, auto_accept, symlinks,
+	return server_register(service, folder, secure, auto_accept, symlinks,
 					capability);
 }
 
diff --git a/obexd/src/bluetooth.h b/obexd/src/bluetooth.h
index 7871177..2c16bd9 100644
--- a/obexd/src/bluetooth.h
+++ b/obexd/src/bluetooth.h
@@ -27,8 +27,7 @@
 #include <config.h>
 #endif
 
-gint bluetooth_init(guint service, const gchar *name, const gchar *folder,
-				guint8 channel, gboolean secure,
+gint bluetooth_init(guint service, const gchar *folder, gboolean secure,
 				gboolean auto_accept, gboolean symlinks,
 				const gchar *capability);
 void bluetooth_exit(void);
diff --git a/obexd/src/main.c b/obexd/src/main.c
index abd2bcc..dcf13d1 100644
--- a/obexd/src/main.c
+++ b/obexd/src/main.c
@@ -49,6 +49,7 @@
 #include "bluetooth.h"
 #include "obexd.h"
 #include "obex.h"
+#include "service.h"
 
 #define OPP_CHANNEL	9
 #define FTP_CHANNEL	10
@@ -109,7 +110,7 @@ int tty_init(int services, const gchar *root_path,
 	}
 
 	server = g_new0(struct server, 1);
-	server->services = services;
+	server->drivers = obex_service_driver_list(services);
 	server->folder = g_strdup(root_path);
 	server->auto_accept = TRUE;
 	server->capability = g_strdup(capability);
@@ -382,28 +383,26 @@ int main(int argc, char *argv[])
 
 	if (option_opp == TRUE) {
 		services |= OBEX_OPP;
-		bluetooth_init(OBEX_OPP, "Object Push server", option_root,
-				OPP_CHANNEL, FALSE, option_autoaccept,
-				option_symlinks, NULL);
+		bluetooth_init(OBEX_OPP, option_root, FALSE,
+				option_autoaccept, option_symlinks,
+				NULL);
 	}
 
 	if (option_ftp == TRUE) {
 		services |= OBEX_FTP;
-		bluetooth_init(OBEX_FTP, "File Transfer server", option_root,
-				FTP_CHANNEL, TRUE, option_autoaccept,
-				option_symlinks, option_capability);
+		bluetooth_init(OBEX_FTP, option_root, TRUE,
+				option_autoaccept, option_symlinks,
+				option_capability);
 	}
 
 	if (option_pbap == TRUE) {
 		services |= OBEX_PBAP;
-		bluetooth_init(OBEX_PBAP, "Phonebook Access server", NULL,
-				PBAP_CHANNEL, TRUE, FALSE, FALSE, NULL);
+		bluetooth_init(OBEX_PBAP, NULL, TRUE, FALSE, FALSE, NULL);
 	}
 
 	if (option_pcsuite == TRUE) {
 		services |= OBEX_PCSUITE;
-		bluetooth_init(OBEX_PCSUITE, "Nokia OBEX PC Suite Services",
-				option_root, PCSUITE_CHANNEL, TRUE,
+		bluetooth_init(OBEX_PCSUITE, option_root, TRUE,
 				option_autoaccept, option_symlinks,
 				option_capability);
 	}
diff --git a/obexd/src/manager.c b/obexd/src/manager.c
index 95b8701..7ac0eb8 100644
--- a/obexd/src/manager.c
+++ b/obexd/src/manager.c
@@ -43,178 +43,7 @@
 #include "dbus.h"
 #include "logging.h"
 #include "btio.h"
-
-static const gchar *opp_record = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>	\
-<record>									\
-  <attribute id=\"0x0001\">							\
-    <sequence>									\
-      <uuid value=\"0x1105\"/>							\
-    </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=\"0x0009\">							\
-    <sequence>									\
-      <sequence>								\
-        <uuid value=\"0x1105\"/>						\
-        <uint16 value=\"0x0100\" name=\"version\"/>				\
-      </sequence>								\
-    </sequence>									\
-  </attribute>									\
-										\
-  <attribute id=\"0x0100\">							\
-    <text value=\"%s\" name=\"name\"/>						\
-  </attribute>									\
-										\
-  <attribute id=\"0x0303\">							\
-    <sequence>									\
-      <uint8 value=\"0x01\"/>							\
-      <uint8 value=\"0x01\"/>							\
-      <uint8 value=\"0x02\"/>							\
-      <uint8 value=\"0x03\"/>							\
-      <uint8 value=\"0x04\"/>							\
-      <uint8 value=\"0x05\"/>							\
-      <uint8 value=\"0x06\"/>							\
-      <uint8 value=\"0xff\"/>							\
-    </sequence>									\
-  </attribute>									\
-</record>";
-
-static const gchar *ftp_record = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>	\
-<record>									\
-  <attribute id=\"0x0001\">							\
-    <sequence>									\
-      <uuid value=\"0x1106\"/>							\
-    </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=\"0x0009\">							\
-    <sequence>									\
-      <sequence>								\
-        <uuid value=\"0x1106\"/>						\
-        <uint16 value=\"0x0100\" name=\"version\"/>				\
-      </sequence>								\
-    </sequence>									\
-  </attribute>									\
-										\
-  <attribute id=\"0x0100\">							\
-    <text value=\"%s\" name=\"name\"/>						\
-  </attribute>									\
-</record>";
-
-static const gchar *pbap_record = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>	\
-<record>									\
-  <attribute id=\"0x0001\">							\
-    <sequence>									\
-      <uuid value=\"0x112f\"/>							\
-    </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=\"0x0009\">							\
-    <sequence>									\
-      <sequence>								\
-        <uuid value=\"0x1130\"/>						\
-        <uint16 value=\"0x0100\" name=\"version\"/>				\
-      </sequence>								\
-    </sequence>									\
-  </attribute>									\
-										\
-  <attribute id=\"0x0100\">							\
-    <text value=\"%s\" name=\"name\"/>						\
-  </attribute>									\
-										\
-  <attribute id=\"0x0314\">							\
-    <uint8 value=\"0x01\"/>							\
-  </attribute>									\
-</record>";
-
-static const gchar *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>";
+#include "service.h"
 
 #define TRANSFER_INTERFACE OPENOBEX_SERVICE ".Transfer"
 #define SESSION_INTERFACE OPENOBEX_SERVICE ".Session"
@@ -429,6 +258,17 @@ static DBusMessage *unregister_agent(DBusConnection *conn,
 	return dbus_message_new_method_return(msg);
 }
 
+static char *target2str(const uint8_t *t)
+{
+	if (!t)
+		return NULL;
+
+	return g_strdup_printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-"
+				"%02X%02X-%02X%02X%02X%02X%02X%02X",
+				t[0], t[1], t[2], t[3], t[4], t[5], t[6],t[7],
+				t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15]);
+}
+
 static DBusMessage *get_properties(DBusConnection *conn,
 				DBusMessage *msg, void *data)
 {
@@ -436,9 +276,7 @@ static DBusMessage *get_properties(DBusConnection *conn,
 	DBusMessage *reply;
 	DBusMessageIter iter;
 	DBusMessageIter dict;
-	gchar uuid[37];
-	const gchar *ptr = uuid;
-	const uint8_t *t = os->target;
+	gchar *uuid;
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -451,12 +289,11 @@ static DBusMessage *get_properties(DBusConnection *conn,
 			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
 
 	/* Target */
-	sprintf(uuid, "%02X%02X%02X%02X-%02X%02X-%02X%02X-"
-				"%02X%02X-%02X%02X%02X%02X%02X%02X",
-				t[0], t[1], t[2], t[3], t[4], t[5], t[6],t[7],
-				t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15]);
+	uuid = target2str(os->service->target);
 	dbus_message_iter_append_dict_entry(&dict, "Target",
-					DBUS_TYPE_STRING, &ptr);
+					DBUS_TYPE_STRING, &uuid);
+	g_free(uuid);
+
 	/* Root folder */
 	dbus_message_iter_append_dict_entry(&dict, "Root",
 					DBUS_TYPE_STRING, &os->server->folder);
@@ -515,32 +352,6 @@ static GDBusMethodTable session_methods[] = {
 	{ }
 };
 
-static gchar *create_xml_record(const char *name,
-			uint16_t service, uint8_t channel)
-{
-	gchar *xml;
-
-	switch (service) {
-	case OBEX_OPP:
-		xml = g_markup_printf_escaped(opp_record, channel, name);
-		break;
-	case OBEX_FTP:
-		xml = g_markup_printf_escaped(ftp_record, channel, name);
-		break;
-	case OBEX_PBAP:
-		xml = g_markup_printf_escaped(pbap_record, channel, name);
-		break;
-	case OBEX_PCSUITE:
-		xml = g_markup_printf_escaped(pcsuite_record, channel, name);
-		break;
-	default:
-		xml = NULL;
-		break;
-	}
-
-	return xml;
-}
-
 static void add_record_reply(DBusPendingCall *call, gpointer user_data)
 {
 	struct server *server = user_data;
@@ -555,13 +366,17 @@ static void add_record_reply(DBusPendingCall *call, gpointer user_data)
 		dbus_error_free(&derr);
 		handle = 0;
 	} else {
+		struct obex_service_driver *driver;
+
 		dbus_message_get_args(reply, NULL,
 				DBUS_TYPE_UINT32, &handle,
 				DBUS_TYPE_INVALID);
 		server->handle = handle;
 
+		driver = (struct obex_service_driver *) server->drivers->data;
+
 		debug("Registered: %s, handle: 0x%x, folder: %s",
-				server->name, handle, server->folder);
+				driver->name, handle, server->folder);
 	}
 
 	dbus_message_unref(reply);
@@ -596,6 +411,7 @@ failed:
 
 void register_record(struct server *server, gpointer user_data)
 {
+	struct obex_service_driver *driver;
 	gchar *xml;
 	gint ret;
 
@@ -608,7 +424,9 @@ void register_record(struct server *server, gpointer user_data)
 		return;
 	}
 
-	xml = create_xml_record(server->name, server->services, server->channel);
+	driver = (struct obex_service_driver *) server->drivers->data;
+	xml = g_markup_printf_escaped(driver->record, driver->channel,
+					driver->name);
 	ret = add_record(any->path, xml, server);
 	g_free(xml);
 }
@@ -616,7 +434,6 @@ void register_record(struct server *server, gpointer user_data)
 static void find_adapter_any_reply(DBusPendingCall *call, gpointer user_data)
 {
 	DBusMessage *reply = dbus_pending_call_steal_reply(call);
-	struct server *server;
 	const char *path;
 	gchar *xml;
 	GSList *l;
@@ -637,9 +454,12 @@ static void find_adapter_any_reply(DBusPendingCall *call, gpointer user_data)
 	any->path = g_strdup(path);
 
 	for (l = any->servers; l; l = l->next) {
-		server = l->data;
-		xml = create_xml_record(server->name,
-				server->services, server->channel);
+		struct server *server = l->data;
+		struct obex_service_driver *driver;
+
+		driver = (struct obex_service_driver *) server->drivers->data;
+		xml = g_markup_printf_escaped(driver->record, driver->channel,
+						driver->name);
 		add_record(path, xml, server);
 		g_free(xml);
 	}
@@ -1207,3 +1027,5 @@ void unregister_session(guint32 id)
 
 	g_free(path);
 }
+
+
diff --git a/obexd/src/obex.c b/obexd/src/obex.c
index 212c369..9c5cec6 100644
--- a/obexd/src/obex.c
+++ b/obexd/src/obex.c
@@ -48,21 +48,12 @@
 #include "obex.h"
 #include "dbus.h"
 #include "mimetype.h"
+#include "service.h"
 
 /* Default MTU's */
 #define DEFAULT_RX_MTU 32767
 #define DEFAULT_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  };
-
-static const guint8 PBAP_TARGET[TARGET_SIZE] = {
-			0x79, 0x61, 0x35, 0xF0,  0xF0, 0xC5, 0x11, 0xD8,
-			0x09, 0x66, 0x08, 0x00,  0x20, 0x0C, 0x9A, 0x66  };
-
 /* Connection ID */
 static guint32 cid = 0x0000;
 
@@ -74,24 +65,6 @@ typedef struct {
 	guint16 mtu;
 } __attribute__ ((packed)) obex_connect_hdr_t;
 
-struct obex_commands opp = {
-	.get		= opp_get,
-	.put		= opp_put,
-	.chkput		= opp_chkput,
-};
-
-struct obex_commands ftp = {
-	.get		= ftp_get,
-	.put		= ftp_put,
-	.chkput		= ftp_chkput,
-	.setpath	= ftp_setpath,
-};
-
-struct obex_commands pbap = {
-	.get		= pbap_get,
-	.setpath	= pbap_setpath,
-};
-
 static void os_reset_session(struct obex_session *os)
 {
 	if (os->object) {
@@ -146,9 +119,6 @@ static void obex_session_free(struct obex_session *os)
 	if (os->io)
 		g_io_channel_unref(os->io);
 
-	if (os->target && !memcmp(os->target, PBAP_TARGET, TARGET_SIZE))
-		pbap_phonebook_context_destroy(os);
-
 	g_free(os);
 }
 
@@ -233,31 +203,18 @@ static void cmd_connect(struct obex_session *os,
 		if (hi != OBEX_HDR_TARGET || hlen != TARGET_SIZE)
 			continue;
 
-		if (memcmp(hd.bs, FTP_TARGET, TARGET_SIZE) == 0 &&
-				os->server->services &
-						(OBEX_FTP | OBEX_PCSUITE)) {
-			os->target = FTP_TARGET;
-			os->services = OBEX_FTP | OBEX_PCSUITE;
-			os->cmds = &ftp;
-			break;
-		}
-
-		if (memcmp(hd.bs, PBAP_TARGET, TARGET_SIZE) == 0 &&
-				os->server->services & OBEX_PBAP) {
-			os->target = PBAP_TARGET;
-			os->services = OBEX_PBAP;
-			os->cmds = &pbap;
-			pbap_phonebook_context_create(os);
+		os->service = obex_service_driver_find(os->server->drivers, hd.bs);
+		if (os->service)
 			break;
-		}
 
 		error("Connect attempt to a non-supported target");
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
 		return;
 	}
 
-	if (os->target == NULL) {
-		if (os->server->services & OBEX_OPP) {
+	if (os->service == NULL) {
+		os->service = obex_service_driver_find(os->server->drivers, NULL);
+		if (os->service) {
 			register_transfer(os->cid, os);
 			/* OPP doesn't contains target or connection id. */
 			OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
@@ -273,7 +230,7 @@ static void cmd_connect(struct obex_session *os,
 	emit_session_created(cid);
 
 	/* Append received UUID in WHO header */
-	hd.bs = os->target;
+	hd.bs = os->service->target;
 	OBEX_ObjectAddHeader(obex, obj,
 			OBEX_HDR_WHO, hd, TARGET_SIZE,
 			OBEX_FL_FIT_ONE_PACKET);
@@ -296,7 +253,7 @@ static gboolean chk_cid(obex_t *obex, obex_object_t *obj, guint32 cid)
 	os = OBEX_GetUserData(obex);
 
 	/* Object Push doesn't provide a connection id. */
-	if (os->target == NULL)
+	if (os->service == NULL)
 		return TRUE;
 
 	while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) {
@@ -321,10 +278,10 @@ static void cmd_get(struct obex_session *os, obex_t *obex, obex_object_t *obj)
 	guint hlen;
 	guint8 hi;
 
-	if (!os->cmds) {
+	if (!os->service) {
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
 		return;
-	} else if (!os->cmds->get) {
+	} else if (!os->service->get) {
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED,
 				OBEX_RSP_NOT_IMPLEMENTED);
 		return;
@@ -384,13 +341,13 @@ static void cmd_get(struct obex_session *os, obex_t *obex, obex_object_t *obj)
 
 			os->type = g_strndup((const gchar *) hd.bs, hlen);
 			debug("OBEX_HDR_TYPE: %s", os->type);
-			os->driver = obex_mime_type_driver_find(os->target, os->type);
+			os->driver = obex_mime_type_driver_find(os->service->target, os->type);
 			break;
 		}
 	}
 
 	if (!os->driver) {
-		os->driver = obex_mime_type_driver_find(os->target, NULL);
+		os->driver = obex_mime_type_driver_find(os->service->target, NULL);
 		if (!os->driver) {
 			error("No driver found");
 			OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED,
@@ -399,7 +356,7 @@ static void cmd_get(struct obex_session *os, obex_t *obex, obex_object_t *obj)
 		}
 	}
 
-	os->cmds->get(obex, obj);
+	os->service->get(obex, obj);
 }
 
 static void cmd_setpath(struct obex_session *os,
@@ -409,10 +366,10 @@ static void cmd_setpath(struct obex_session *os,
 	guint32 hlen;
 	guint8 hi;
 
-	if (!os->cmds) {
+	if (!os->service) {
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
 		return;
-	} else if (!os->cmds->setpath) {
+	} else if (!os->service->setpath) {
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED,
 				OBEX_RSP_NOT_IMPLEMENTED);
 		return;
@@ -448,7 +405,7 @@ static void cmd_setpath(struct obex_session *os,
 		break;
 	}
 
-	os->cmds->setpath(obex, obj);
+	os->service->setpath(obex, obj);
 }
 
 int os_prepare_get(struct obex_session *os, gchar *filename, size_t *size)
@@ -456,11 +413,6 @@ int os_prepare_get(struct obex_session *os, gchar *filename, size_t *size)
 	gint err;
 	gpointer object;
 
-	if (!os->driver) {
-		error("No driver to handle %s", os->type);
-		return -ENOENT;
-	}
-
 	object = os->driver->open(filename, O_RDONLY, 0, size);
 	if (object == NULL) {
 		err = -errno;
@@ -496,7 +448,6 @@ static gint obex_write_stream(struct obex_session *os,
 	if (os->aborted)
 		return -EPERM;
 
-
 	if (os->object == NULL) {
 		if (os->buf == NULL && os->finished == FALSE)
 			return -EIO;
@@ -534,10 +485,6 @@ add_header:
 	OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_BODY, hd, len,
 				OBEX_FL_STREAM_DATA);
 
-	if (!memcmp(os->target, PBAP_TARGET, TARGET_SIZE))
-		if (os->offset == os->size && os->finished == FALSE)
-			OBEX_SuspendRequest(obex, obj);
-
 	return len;
 }
 
@@ -558,7 +505,7 @@ gint os_prepare_put(struct obex_session *os)
 
 	g_free(path);
 
-	if (os->target == NULL)
+	if (os->service == NULL)
 		emit_transfer_started(os->cid);
 
 	if (!os->buf) {
@@ -703,7 +650,7 @@ static gboolean check_put(obex_t *obex, obex_object_t *obj)
 
 			os->type = g_strndup((const gchar *) hd.bs, hlen);
 			debug("OBEX_HDR_TYPE: %s", os->type);
-			os->driver = obex_mime_type_driver_find(os->target, os->type);
+			os->driver = obex_mime_type_driver_find(os->service->target, os->type);
 			break;
 
 		case OBEX_HDR_BODY:
@@ -732,7 +679,7 @@ static gboolean check_put(obex_t *obex, obex_object_t *obj)
 	}
 
 	if (!os->driver) {
-		os->driver = obex_mime_type_driver_find(os->target, NULL);
+		os->driver = obex_mime_type_driver_find(os->service->target, NULL);
 		if (!os->driver) {
 			error("No driver found");
 			OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED,
@@ -741,10 +688,10 @@ static gboolean check_put(obex_t *obex, obex_object_t *obj)
 		}
 	}
 
-	if (!os->cmds || !os->cmds->chkput)
+	if (!os->service || !os->service->chkput)
 		goto done;
 
-	ret = os->cmds->chkput(obex, obj);
+	ret = os->service->chkput(obex, obj);
 	switch (ret) {
 	case 0:
 		break;
@@ -775,10 +722,10 @@ done:
 
 static void cmd_put(struct obex_session *os, obex_t *obex, obex_object_t *obj)
 {
-	if (!os->cmds) {
+	if (!os->service) {
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
 		return;
-	} else if (!os->cmds->put) {
+	} else if (!os->service->put) {
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_IMPLEMENTED,
 				OBEX_RSP_NOT_IMPLEMENTED);
 		return;
@@ -791,7 +738,7 @@ static void cmd_put(struct obex_session *os, obex_t *obex, obex_object_t *obj)
 			return;
 	}
 
-	os->cmds->put(obex, obj);
+	os->service->put(obex, obj);
 }
 
 static void obex_event(obex_t *obex, obex_object_t *obj, gint mode,
@@ -806,12 +753,12 @@ static void obex_event(obex_t *obex, obex_object_t *obj, gint mode,
 	switch (evt) {
 	case OBEX_EV_PROGRESS:
 		/* Just emit progress for Object Push */
-		if (os->target == NULL)
+		if (os->service == NULL)
 			emit_transfer_progress(os->cid, os->size, os->offset);
 		break;
 	case OBEX_EV_ABORT:
 		os->aborted = TRUE;
-		if (os->target == NULL)
+		if (os->service == NULL)
 			emit_transfer_completed(os->cid, FALSE);
 		os_reset_session(os);
 		OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
@@ -824,7 +771,7 @@ static void obex_event(obex_t *obex, obex_object_t *obj, gint mode,
 		case OBEX_CMD_PUT:
 		case OBEX_CMD_GET:
 			os_session_mark_aborted(os);
-			if (os->target == NULL)
+			if (os->service == NULL)
 				emit_transfer_completed(os->cid, !os->aborted);
 			os_reset_session(os);
 			break;
@@ -854,7 +801,7 @@ static void obex_event(obex_t *obex, obex_object_t *obj, gint mode,
 	case OBEX_EV_REQCHECK:
 		switch (cmd) {
 		case OBEX_CMD_PUT:
-			if (os->cmds && os->cmds->put)
+			if (os->service && os->service->put)
 				check_put(obex, obj);
 			break;
 		default:
@@ -918,7 +865,6 @@ static void obex_event(obex_t *obex, obex_object_t *obj, gint mode,
 
 void server_free(struct server *server)
 {
-	g_free(server->name);
 	g_free(server->folder);
 	g_free(server->capability);
 	g_free(server->devnode);
@@ -932,7 +878,7 @@ static void obex_handle_destroy(gpointer user_data)
 
 	os = OBEX_GetUserData(obex);
 
-	if (os->target == NULL) {
+	if (os->service == NULL) {
 		/* Got an error during a transfer. */
 		if (os->object)
 			emit_transfer_completed(os->cid, os->offset == os->size);
@@ -951,9 +897,17 @@ static void obex_handle_destroy(gpointer user_data)
 static gboolean tty_reinit(gpointer data)
 {
 	struct server *server = data;
+	GSList *l;
+	guint services = 0;
+
+	for (l = server->drivers; l; l = l->next) {
+		struct obex_service_driver *driver = l->data;
+
+		services |= driver->service;
+	}
 
-	tty_init(server->services, server->folder, server->capability,
-					server->symlinks, server->devnode);
+	tty_init(services, server->folder, server->capability,
+			server->symlinks, server->devnode);
 
 	server_free(server);
 
@@ -1000,10 +954,9 @@ gint obex_session_start(GIOChannel *io, struct server *server)
 
 	os = g_new0(struct obex_session, 1);
 
-	os->target = NULL;
+	os->service = NULL;
 
-	if (server->services & OBEX_OPP)
-		os->cmds = &opp;
+	os->service = obex_service_driver_find(server->drivers, NULL);
 
 	os->current_folder = g_strdup(server->folder);
 	os->server = server;
diff --git a/obexd/src/obex.h b/obexd/src/obex.h
index 13ecf81..eb4cb1a 100644
--- a/obexd/src/obex.h
+++ b/obexd/src/obex.h
@@ -29,8 +29,6 @@
 
 #include <glib.h>
 
-#include "phonebook.h"
-
 #define OBJECT_SIZE_UNKNOWN -1
 #define OBJECT_SIZE_DELETE -2
 
@@ -40,39 +38,32 @@
 #define OBEX_PBAP	(1 << 4)
 #define OBEX_PCSUITE	(1 << 5)
 
-struct obex_mime_type_driver;
+#define TARGET_SIZE 16
 
-struct obex_commands {
-	void (*get) (obex_t *obex, obex_object_t *obj);
-	void (*put) (obex_t *obex, obex_object_t *obj);
-	gint (*chkput) (obex_t *obex, obex_object_t *obj);
-	void (*setpath) (obex_t *obex, obex_object_t *obj);
-};
+struct obex_service_driver;
+struct obex_mime_type_driver;
 
 struct server {
-	guint16		services;
 	gboolean	auto_accept;
-	gchar		*name;
 	gchar		*folder;
 	gboolean	symlinks;
 	gchar		*capability;
 	guint32		handle;
-	uint8_t		channel;
 	gchar		*devnode;
 	gboolean	secure;
 	GIOChannel	*io;
 	guint		watch;
 	guint16		tx_mtu;
 	guint16		rx_mtu;
+	GSList		*drivers;
 };
 
 struct obex_session {
 	GIOChannel	*io;
 	guint32		cid;
-	guint16		services;
 	guint16		tx_mtu;
 	guint16		rx_mtu;
-	uint8_t		cmd;
+	guint8		cmd;
 	gchar		*name;
 	gchar		*type;
 	time_t		time;
@@ -82,12 +73,10 @@ struct obex_session {
 	gint32		size;
 	gpointer	object;
 	gboolean	aborted;
-	const guint8	*target;
-	struct obex_commands *cmds;
+	struct obex_service_driver *service;
 	struct server *server;
 	gboolean	checked;
 	obex_t		*obex;
-	struct phonebook_context *pbctx;
 	struct obex_mime_type_driver *driver;
 	gboolean	finished;
 };
@@ -96,21 +85,6 @@ gint obex_session_start(GIOChannel *io, struct server *server);
 struct obex_session *obex_get_session(gpointer object);
 gint obex_tty_session_stop(void);
 
-void opp_get(obex_t *obex, obex_object_t *obj);
-void opp_put(obex_t *obex, obex_object_t *obj);
-gint opp_chkput(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);
-gint ftp_chkput(obex_t *obex, obex_object_t *obj);
-void ftp_setpath(obex_t *obex, obex_object_t *obj);
-
-void pbap_get(obex_t *obex, obex_object_t *obj);
-void pbap_setpath(obex_t *obex, obex_object_t *obj);
-gboolean pbap_phonebook_context_create(struct obex_session *session);
-void pbap_phonebook_context_destroy(struct obex_session *session);
-struct obex_session *pbap_get_session(struct phonebook_context *context);
-
 gint os_prepare_get(struct obex_session *os, gchar *file, size_t *size);
 gint os_prepare_put(struct obex_session *os);
 
diff --git a/obexd/src/pbap.c b/obexd/src/pbap.c
deleted file mode 100644
index 5f1fb73..0000000
--- a/obexd/src/pbap.c
+++ /dev/null
@@ -1,528 +0,0 @@
-/*
- *
- *  OBEX Server
- *
- *  Copyright (C) 2007-2009  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 <errno.h>
-#include <glib.h>
-#include <bluetooth/bluetooth.h>
-
-#include <openobex/obex.h>
-#include <openobex/obex_const.h>
-
-#include "logging.h"
-#include "obex.h"
-
-#define PHONEBOOK_TYPE		"x-bt/phonebook"
-#define VCARDLISTING_TYPE	"x-bt/vcard-listing"
-#define VCARDENTRY_TYPE		"x-bt/vcard"
-
-#define ORDER_TAG		0x01
-#define SEARCHVALUE_TAG		0x02
-#define SEARCHATTRIB_TAG	0x03
-#define MAXLISTCOUNT_TAG	0x04
-#define LISTSTARTOFFSET_TAG	0x05
-#define FILTER_TAG		0x06
-#define FORMAT_TAG		0X07
-#define PHONEBOOKSIZE_TAG	0X08
-#define NEWMISSEDCALLS_TAG	0X09
-
-/* The following length is in the unit of byte */
-#define ORDER_LEN		1
-#define SEARCHATTRIB_LEN	1
-#define MAXLISTCOUNT_LEN	2
-#define LISTSTARTOFFSET_LEN	2
-#define FILTER_LEN		8
-#define FORMAT_LEN		1
-#define PHONEBOOKSIZE_LEN	2
-#define NEWMISSEDCALLS_LEN	1
-
-#define MCH		"telecom/mch.vcf"
-#define SIM1_MCH	"SIM1/telecom/mch.vcf"
-
-struct apparam_hdr {
-	uint8_t		tag;
-	uint8_t		len;
-	uint8_t		val[0];
-} __attribute__ ((packed));
-#define APPARAM_HDR_SIZE 2
-
-#define get_be64(val)	GUINT64_FROM_BE(bt_get_unaligned((guint64 *) val))
-#define get_be16(val)	GUINT16_FROM_BE(bt_get_unaligned((guint16 *) val))
-
-#define put_be16(val, ptr) bt_put_unaligned(GUINT16_TO_BE(val), (guint16 *) ptr)
-
-struct apparam_field {
-	guint64		filter;
-	guint16		maxlistcount;
-	guint16		liststartoffset;
-	guint8		format;
-	guint8		order;
-	guint8		searchattrib;
-	guint8		*searchval;
-};
-
-static GSList *session_list = NULL;
-
-static int pbap_parse_apparam_header(obex_t *obex, obex_object_t *obj,
-						struct apparam_field *apparam)
-{
-	obex_headerdata_t hd;
-	guint8 hi;
-	guint32 hlen;
-
-	while (OBEX_ObjectGetNextHeader(obex, obj, &hi, &hd, &hlen)) {
-		void *ptr = (void *) hd.bs;
-		uint32_t len = hlen;
-
-		if (hi != OBEX_HDR_APPARAM)
-			continue;
-
-		if (hlen < APPARAM_HDR_SIZE) {
-			g_free(apparam->searchval);
-			error("PBAP pullphonebook app parameters header"
-						" is too short: %d", hlen);
-			return -1;
-		}
-
-		while (len > APPARAM_HDR_SIZE) {
-			struct apparam_hdr *hdr = ptr;
-
-			if (hdr->len > len - APPARAM_HDR_SIZE) {
-				g_free(apparam->searchval);
-				error("Unexpected PBAP pullphonebook app"
-						" length, tag %d, len %d",
-							hdr->tag, hdr->len);
-				return -1;
-			}
-
-			switch (hdr->tag) {
-			case ORDER_TAG:
-				if (hdr->len == ORDER_LEN)
-					apparam->order = hdr->val[0];
-				break;
-			case SEARCHATTRIB_TAG:
-				if (hdr->len == SEARCHATTRIB_LEN)
-					apparam->searchattrib = hdr->val[0];
-				break;
-			case SEARCHVALUE_TAG:
-				apparam->searchval = g_try_malloc(hdr->len + 1);
-				if (apparam->searchval != NULL) {
-					memcpy(apparam->searchval, hdr->val,
-								hdr->len);
-					apparam->searchval[hdr->len] = '\0';
-				}
-				break;
-			case FILTER_TAG:
-				if (hdr->len == FILTER_LEN) {
-					guint64 val;
-					memcpy(&val, hdr->val, sizeof(val));
-					apparam->filter = get_be64(&val);
-				}
-				break;
-			case FORMAT_TAG:
-				if (hdr->len == FORMAT_LEN)
-					apparam->format = hdr->val[0];
-				break;
-			case MAXLISTCOUNT_TAG:
-				if (hdr->len == MAXLISTCOUNT_LEN) {
-					guint16 val;
-					memcpy(&val, hdr->val, sizeof(val));
-					apparam->maxlistcount = get_be16(&val);
-				}
-				break;
-			case LISTSTARTOFFSET_TAG:
-				if (hdr->len == LISTSTARTOFFSET_LEN) {
-					guint16 val;
-					memcpy(&val, hdr->val, sizeof(val));
-					apparam->liststartoffset = get_be16(&val);
-				}
-				break;
-			default:
-				g_free(apparam->searchval);
-				error("Unexpected PBAP pullphonebook app"
-						" parameter, tag %d, len %d",
-							hdr->tag, hdr->len);
-				return -1;
-			}
-
-			ptr += APPARAM_HDR_SIZE + hdr->len;
-			len -= APPARAM_HDR_SIZE + hdr->len;
-		}
-
-		/* Ignore multiple app param headers */
-		break;
-	}
-
-	return 0;
-}
-
-/* Add app parameter header, that is sent back to PBAP client */
-static int pbap_add_result_apparam_header(obex_t *obex, obex_object_t *obj,
-				guint16 maxlistcount, gchar *path_name,
-				guint16 phonebooksize,
-				guint8 newmissedcalls, gboolean *addbody)
-{
-	guint8 rspsize = 0;
-	gboolean addmissedcalls = FALSE;
-	obex_headerdata_t hd;
-
-	if (maxlistcount == 0) {
-		rspsize += APPARAM_HDR_SIZE + PHONEBOOKSIZE_LEN;
-		*addbody = FALSE;
-	}
-
-	if (g_str_equal(path_name, SIM1_MCH) == TRUE ||
-				g_str_equal(path_name, MCH) == TRUE) {
-		rspsize += APPARAM_HDR_SIZE + NEWMISSEDCALLS_LEN;
-		addmissedcalls = TRUE;
-	}
-
-	if (rspsize > 0) {
-		void *buf, *ptr;
-
-		buf = g_try_malloc0(rspsize);
-		if (buf == NULL)
-			return -ENOMEM;
-
-		ptr = buf;
-
-		if (maxlistcount == 0) {
-			struct apparam_hdr *hdr = ptr;
-			guint16 val = GUINT16_TO_BE(phonebooksize);
-
-			hdr->tag = PHONEBOOKSIZE_TAG;
-			hdr->len = PHONEBOOKSIZE_LEN;
-			memcpy(hdr->val, &val, sizeof(val));
-
-			ptr += APPARAM_HDR_SIZE + PHONEBOOKSIZE_LEN;
-		}
-
-		if (addmissedcalls == TRUE) {
-			struct apparam_hdr *hdr = ptr;
-
-			hdr->tag = NEWMISSEDCALLS_TAG;
-			hdr->len = NEWMISSEDCALLS_LEN;
-			hdr->val[0] = newmissedcalls;
-
-			ptr += APPARAM_HDR_SIZE + NEWMISSEDCALLS_LEN;
-		}
-
-		hd.bs = buf;
-		OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_APPARAM,
-							hd, rspsize, 0);
-		g_free(buf);
-	}
-
-	return 0;
-}
-
-static int pbap_pullphonebook(obex_t *obex, obex_object_t *obj,
-							gboolean *addbody)
-{
-	struct obex_session *session = OBEX_GetUserData(obex);
-	struct apparam_field apparam;
-	guint8 newmissedcalls = 0;
-	guint16 phonebooksize = 0;
-	int err;
-
-	memset(&apparam, 0, sizeof(struct apparam_field));
-
-	err = pbap_parse_apparam_header(obex, obj, &apparam);
-	if (err < 0)
-		return err;
-
-	err = phonebook_pullphonebook(session->pbctx, session->name,
-					apparam.filter, apparam.format,
-					apparam.maxlistcount,
-					apparam.liststartoffset,
-					&phonebooksize, &newmissedcalls);
-	if (err < 0)
-		return err;
-
-	return pbap_add_result_apparam_header(obex, obj, apparam.maxlistcount,
-						session->name, phonebooksize,
-						newmissedcalls, addbody);
-}
-
-static int pbap_pullvcardlisting(obex_t *obex, obex_object_t *obj,
-							gboolean *addbody)
-{
-	struct obex_session *session = OBEX_GetUserData(obex);
-	gchar *fullname;
-	struct apparam_field apparam;
-	guint8 newmissedcalls = 0;
-	guint16 phonebooksize = 0;
-	int err;
-
-	memset(&apparam, 0, sizeof(struct apparam_field));
-
-	err = pbap_parse_apparam_header(obex, obj, &apparam);
-	if (err < 0)
-		return err;
-
-	fullname = g_build_filename(session->current_folder,
-						session->name, NULL);
-	err = phonebook_pullvcardlisting(session->pbctx, fullname,
-					apparam.order, apparam.searchval,
-					apparam.searchattrib,
-					apparam.maxlistcount,
-					apparam.liststartoffset,
-					&phonebooksize, &newmissedcalls);
-	if (err < 0)
-		goto done;
-
-	g_free(fullname);
-
-	fullname = g_build_filename(session->current_folder, session->name,
-								NULL);
-	if (fullname != NULL)
-		fullname = g_strconcat(fullname, ".vcf", NULL);
-
-	err = pbap_add_result_apparam_header(obex, obj, apparam.maxlistcount,
-						fullname, phonebooksize,
-						newmissedcalls, addbody);
-
-done:
-	g_free(apparam.searchval);
-	g_free(fullname);
-	return err;
-}
-
-static int pbap_pullvcardentry(obex_t *obex, obex_object_t *obj)
-{
-	struct obex_session *session = OBEX_GetUserData(obex);
-	gchar *fullname;
-	struct apparam_field apparam;
-	int err;
-
-	memset(&apparam, 0, sizeof(struct apparam_field));
-	err = pbap_parse_apparam_header(obex, obj, &apparam);
-	if (err < 0)
-		return err;
-
-	fullname = g_build_filename(session->current_folder,
-						session->name, NULL);
-	err = phonebook_pullvcardentry(session->pbctx, fullname,
-					apparam.filter, apparam.format);
-
-	g_free(apparam.searchval);
-	g_free(fullname);
-	return err;
-}
-
-void pbap_get(obex_t *obex, obex_object_t *obj)
-{
-	struct obex_session *session = OBEX_GetUserData(obex);
-	obex_headerdata_t hd;
-	gboolean addbody = TRUE;
-	int err;
-
-	if (session == NULL)
-		return;
-
-	if (session->type == NULL)
-		goto fail;
-
-	if (g_str_equal(session->type, VCARDLISTING_TYPE) == FALSE
-						&& session->name == NULL)
-		goto fail;
-
-	OBEX_ObjectReParseHeaders(obex, obj);
-
-	if (g_str_equal(session->type, PHONEBOOK_TYPE) == TRUE)
-		err = pbap_pullphonebook(obex, obj, &addbody);
-	else if (g_str_equal(session->type, VCARDLISTING_TYPE) == TRUE)
-		err = pbap_pullvcardlisting(obex, obj, &addbody);
-	else if (g_str_equal(session->type, VCARDENTRY_TYPE) == TRUE)
-		err = pbap_pullvcardentry(obex, obj);
-	else
-		goto fail;
-
-	if (err < 0)
-		goto fail;
-
-	if (addbody == TRUE) {
-		OBEX_SuspendRequest(obex, obj);
-		session->size = 0;
-
-		/* Add body header */
-		hd.bs = NULL;
-		OBEX_ObjectAddHeader(obex, obj, OBEX_HDR_BODY,
-						hd, 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);
-}
-
-static gboolean pbap_is_valid_folder(struct obex_session *session)
-{
-	if (session->current_folder == NULL) {
-		if (g_str_equal(session->name, "telecom") == TRUE ||
-			g_str_equal(session->name, "SIM1") == TRUE)
-			return TRUE;
-	} else if (g_str_equal(session->current_folder, "SIM1") == TRUE) {
-		if (g_str_equal(session->name, "telecom") == TRUE)
-			return TRUE;
-	} else if (g_str_equal(session->current_folder, "telecom") == TRUE ||
-		g_str_equal(session->current_folder, "SIM1/telecom") == TRUE) {
-		if (g_str_equal(session->name, "pb") == TRUE ||
-				g_str_equal(session->name, "ich") == TRUE ||
-				g_str_equal(session->name, "och") == TRUE ||
-				g_str_equal(session->name, "mch") == TRUE ||
-				g_str_equal(session->name, "cch") == TRUE)
-			return TRUE;
-	}
-
-	return FALSE;
-}
-
-void pbap_setpath(obex_t *obex, obex_object_t *obj)
-{
-	struct obex_session *session = OBEX_GetUserData(obex);
-	guint8 *nonhdr;
-	gchar *fullname;
-
-	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 "Backup" flag */
-	if ((nonhdr[0] & 0x01) == 0x01) {
-		debug("Set to parent path");
-
-		if (session->current_folder == NULL) {
-			/* we are already in top level folder */
-			OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN,
-					OBEX_RSP_FORBIDDEN);
-			return;
-		}
-
-		fullname = g_path_get_dirname(session->current_folder);
-		g_free(session->current_folder);
-
-		if (strlen(fullname) == 1 && *fullname == '.')
-			session->current_folder = NULL;
-		else
-			session->current_folder = g_strdup(fullname);
-
-		g_free(fullname);
-
-		debug("Set to parent path: %s", session->current_folder);
-
-		OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
-		return;
-	}
-
-	if (!session->name) {
-		OBEX_ObjectSetRsp(obj, OBEX_RSP_CONTINUE, OBEX_RSP_BAD_REQUEST);
-		error("Set path failed: name missing!");
-		return;
-	}
-
-	if (strlen(session->name) == 0) {
-		debug("Set to root");
-
-		g_free(session->current_folder);
-		session->current_folder = NULL;
-
-		OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
-		return;
-	}
-
-	/* Check and set to name path */
-	if (strstr(session->name, "/")) {
-		OBEX_ObjectSetRsp(obj, OBEX_RSP_FORBIDDEN, OBEX_RSP_FORBIDDEN);
-		error("Set path failed: name incorrect!");
-		return;
-	}
-
-	if (pbap_is_valid_folder(session) == FALSE) {
-		OBEX_ObjectSetRsp(obj, OBEX_RSP_NOT_FOUND, OBEX_RSP_NOT_FOUND);
-		return;
-	}
-
-	if (session->current_folder == NULL)
-		fullname = g_build_filename("", session->name, NULL);
-	else
-		fullname = g_build_filename(session->current_folder, session->name, NULL);
-
-	debug("Fullname: %s", fullname);
-
-	g_free(session->current_folder);
-	session->current_folder = fullname;
-	OBEX_ObjectSetRsp(obj, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS);
-}
-
-gboolean pbap_phonebook_context_create(struct obex_session *session)
-{
-	struct phonebook_context *context;
-	struct phonebook_driver *driver;
-
-	driver = phonebook_get_driver(NULL);
-	if (driver == NULL)
-		return FALSE;
-
-	context = phonebook_create(driver);
-	if (context == NULL)
-		return FALSE;
-
-	session->pbctx = context;
-
-	session_list = g_slist_append(session_list, session);
-
-	return TRUE;
-}
-
-void pbap_phonebook_context_destroy(struct obex_session *session)
-{
-	struct phonebook_context *context;
-
-	context = session->pbctx;
-	phonebook_unref(context);
-
-	session_list = g_slist_remove(session_list, session);
-}
-
-struct obex_session *pbap_get_session(struct phonebook_context *context)
-{
-	GSList *current;
-
-	for (current = session_list; current != NULL; current = current->next) {
-		struct obex_session *session = current->data;
-		if (session->pbctx == context)
-			return session;
-	}
-
-	return NULL;
-}
diff --git a/obexd/src/phonebook.c b/obexd/src/phonebook.c
deleted file mode 100644
index cdbc319..0000000
--- a/obexd/src/phonebook.c
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- *
- *  OBEX Server
- *
- *  Copyright (C) 2007-2009  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 <string.h>
-#include <glib.h>
-#include <stdlib.h>
-
-#include <openobex/obex.h>
-#include <openobex/obex_const.h>
-
-#include "logging.h"
-#include "obex.h"
-
-static GSList *driver_list = NULL;
-
-int phonebook_driver_register(struct phonebook_driver *driver)
-{
-	DBG("driver %p name %s", driver, driver->name);
-
-	driver_list = g_slist_append(driver_list, driver);
-
-	return 0;
-}
-
-void phonebook_driver_unregister(struct phonebook_driver *driver)
-{
-	DBG("driver %p name %s", driver, driver->name);
-
-	driver_list = g_slist_remove(driver_list, driver);
-}
-
-struct phonebook_context *phonebook_create(struct phonebook_driver *driver)
-{
-	struct phonebook_context *context;
-
-	if (driver == NULL)
-		return NULL;
-
-	context = g_try_new0(struct phonebook_context, 1);
-	if (context == NULL)
-		return NULL;
-
-	DBG("context %p", context);
-
-	context->refcount = 1;
-	context->driver = driver;
-
-	if (driver->create) {
-		if (driver->create(context) < 0) {
-			g_free(context);
-			return NULL;
-		}
-	}
-
-	return context;
-}
-
-struct phonebook_context *phonebook_ref(struct phonebook_context *context)
-{
-	DBG("context %p refcount %d", context,
-				g_atomic_int_get(&context->refcount) + 1);
-
-	g_atomic_int_inc(&context->refcount);
-
-	return context;
-}
-
-void phonebook_unref(struct phonebook_context *context)
-{
-	DBG("context %p refcount %d", context,
-				g_atomic_int_get(&context->refcount) - 1);
-
-	if (g_atomic_int_dec_and_test(&context->refcount) == TRUE) {
-		if (context->driver->destroy)
-			context->driver->destroy(context);
-		g_free(context);
-	}
-}
-
-int phonebook_pullphonebook(struct phonebook_context *context, gchar *objname,
-			guint64 filter, guint8 format, guint16 maxlistcount,
-			guint16 liststartoffset, guint16 *phonebooksize,
-			guint8 *newmissedcalls)
-{
-	if (!context->driver->pullphonebook)
-		return -1;
-
-	return context->driver->pullphonebook(context, objname, filter, format,
-				maxlistcount, liststartoffset, phonebooksize,
-				newmissedcalls);
-}
-
-int phonebook_pullvcardlisting(struct phonebook_context *context,
-			gchar *objname, guint8 order, guint8 *searchval,
-			guint8 searchattrib, guint16 maxlistcount,
-			guint16 liststartoffset, guint16 *phonebooksize,
-			guint8 *newmissedcalls)
-{
-	if (!context->driver->pullvcardlisting)
-		return -1;
-
-	return context->driver->pullvcardlisting(context, objname, order,
-				searchval, searchattrib, maxlistcount,
-				liststartoffset, phonebooksize, newmissedcalls);
-}
-
-int phonebook_pullvcardentry(struct phonebook_context *context, gchar *objname,
-						guint64 filter, guint8 format)
-{
-	if (!context->driver->pullvcardentry)
-		return -1;
-
-	return context->driver->pullvcardentry(context, objname, filter,
-								format);
-}
-
-/* if buf is NULL or size is 0, this indicate that no more result will
- * be returned by PBAP plugin
- * */
-void phonebook_return(struct phonebook_context *context,
-						char *buf, int size)
-{
-	struct obex_session *session;
-
-	DBG("context %p", context);
-
-	session = pbap_get_session(context);
-
-	if (buf == NULL || size == 0) {
-		session->finished = 1;
-		OBEX_ResumeRequest(session->obex);
-		return;
-	}
-
-	session->buf = g_realloc(session->buf, session->size + size);
-	memcpy(session->buf + session->size, buf, size);
-	session->size += size;
-
-	OBEX_ResumeRequest(session->obex);
-}
-
-struct phonebook_driver *phonebook_get_driver(const char *name)
-{
-	DBG("name %s", name);
-
-	return g_slist_nth_data(driver_list, 0);
-}
diff --git a/obexd/src/phonebook.h b/obexd/src/phonebook.h
deleted file mode 100644
index ecf5e4f..0000000
--- a/obexd/src/phonebook.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- *
- *  OBEX Server
- *
- *  Copyright (C) 2007-2009  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
- *
- */
-
-#include <glib.h>
-
-struct phonebook_driver;
-
-struct phonebook_context {
-	gint refcount;
-
-	struct phonebook_driver *driver;
-	void *driver_data;
-};
-
-extern struct phonebook_context *phonebook_create(
-			struct phonebook_driver *driver);
-extern struct phonebook_context *phonebook_ref(
-			struct phonebook_context *context);
-extern void phonebook_unref(struct phonebook_context *context);
-
-static inline void *phonebook_get_data(struct phonebook_context *context)
-{
-	return context->driver_data;
-}
-
-static inline void phonebook_set_data(struct phonebook_context *context,
-								void *data)
-{
-	context->driver_data = data;
-}
-
-extern int phonebook_pullphonebook(struct phonebook_context *context,
-			gchar *objname, guint64 filter, guint8 format,
-			guint16 maxlistcount, guint16 liststartoffset,
-			guint16 *phonebooksize, guint8 *newmissedcalls);
-extern int phonebook_pullvcardlisting(struct phonebook_context *context,
-			gchar *objname, guint8 order, guint8 *searchval,
-			guint8 searchattrib, guint16 maxlistcount,
-			guint16 liststartoffset, guint16 *phonebooksize,
-			guint8 *newmissedcalls);
-extern int phonebook_pullvcardentry(struct phonebook_context *context,
-			gchar *objname, guint64 filter, guint8 format);
-extern void phonebook_return(struct phonebook_context *context,
-						char *buf, int size);
-
-struct phonebook_driver {
-	const char *name;
-	int (*create) (struct phonebook_context *context);
-	void (*destroy) (struct phonebook_context *context);
-	int (*pullphonebook) (struct phonebook_context *context,
-			gchar *objname, guint64 filter, guint8 format,
-			guint16 maxlistcount, guint16 liststartoffset,
-			guint16 *phonebooksize, guint8 *newmissedcalls);
-	int (*pullvcardlisting) (struct phonebook_context *context,
-			gchar *objname, guint8 order, guint8 *searchval,
-			guint8 searchattrib, guint16 maxlistcount,
-			guint16 liststartoffset, guint16 *phonebooksize,
-			guint8 *newmissedcalls);
-	int (*pullvcardentry) (struct phonebook_context *context,
-			gchar *objname, guint64 filter, guint8 format);
-};
-
-extern int phonebook_driver_register(struct phonebook_driver *driver);
-extern void phonebook_driver_unregister(struct phonebook_driver *driver);
-
-struct phonebook_driver *phonebook_get_driver(const char *name);
diff --git a/obexd/src/service.c b/obexd/src/service.c
new file mode 100644
index 0000000..2a5494d
--- /dev/null
+++ b/obexd/src/service.c
@@ -0,0 +1,106 @@
+/*
+ *
+ *  OBEX Server
+ *
+ *  Copyright (C) 2007-2009  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 <string.h>
+#include <errno.h>
+#include <glib.h>
+
+#include <openobex/obex.h>
+#include <openobex/obex_const.h>
+
+#include "service.h"
+#include "logging.h"
+#include "obex.h"
+
+static GSList *drivers = NULL;
+
+struct obex_service_driver *obex_service_driver_find(GSList *list, const guint8 *target)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct obex_service_driver *driver = l->data;
+
+		if (driver->target == NULL && target == NULL)
+			return driver;
+
+		if (driver->target && target &&
+				memcmp(driver->target, target, TARGET_SIZE) == 0)
+			return driver;
+	}
+
+	return NULL;
+}
+
+GSList *obex_service_driver_list(guint16 services)
+{
+	GSList *l;
+	GSList *list = NULL;
+
+	for (l = drivers; l && services; l = l->next) {
+		struct obex_service_driver *driver = l->data;
+
+		if (driver->service & services) {
+			list = g_slist_append(list, driver);
+			services &= ~driver->service;
+		}
+	}
+
+	return list;
+}
+
+int obex_service_driver_register(struct obex_service_driver *driver)
+{
+	if (!driver) {
+		error("Invalid driver");
+		return -EINVAL;
+	}
+
+	if (obex_service_driver_list(driver->service)) {
+		error("Permission denied: service %s already registered",
+			driver->name);
+		return -EPERM;
+	}
+
+	debug("driver %p service %s registered", driver, driver->name);
+
+	drivers = g_slist_append(drivers, driver);
+
+	return 0;
+}
+
+void obex_service_driver_unregister(struct obex_service_driver *driver)
+{
+	if (!g_slist_find(drivers, driver)) {
+		error("Unable to unregister: No such driver %p", driver);
+		return;
+	}
+
+	debug("driver %p service %s unregistered", driver, driver->name);
+
+	drivers = g_slist_remove(drivers, driver);
+}
diff --git a/obexd/src/service.h b/obexd/src/service.h
new file mode 100644
index 0000000..2cf3443
--- /dev/null
+++ b/obexd/src/service.h
@@ -0,0 +1,40 @@
+/*
+ *
+ *  OBEX Server
+ *
+ *  Copyright (C) 2007-2009  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
+ *
+ */
+
+struct obex_service_driver {
+	const char *name;
+	guint16 service;
+	guint8 channel;
+	const guint8 *target;
+	const gchar *record;
+	void (*get) (obex_t *obex, obex_object_t *obj);
+	void (*put) (obex_t *obex, obex_object_t *obj);
+	gint (*chkput) (obex_t *obex, obex_object_t *obj);
+	void (*setpath) (obex_t *obex, obex_object_t *obj);
+};
+
+int obex_service_driver_register(struct obex_service_driver *driver);
+void obex_service_driver_unregister(struct obex_service_driver *driver);
+GSList *obex_service_driver_list(guint16 services);
+struct obex_service_driver *obex_service_driver_find(GSList *drivers,
+						const guint8 *uuid);