| /* | |
| * | |
| * OBEX IrMC Sync Server | |
| * | |
| * Copyright (C) 2010 Marcel Mol <marcel@mesa.nl> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <glib.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <arpa/inet.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <inttypes.h> | |
| #include "obexd/src/obexd.h" | |
| #include "obexd/src/plugin.h" | |
| #include "obexd/src/log.h" | |
| #include "obexd/src/obex.h" | |
| #include "obexd/src/service.h" | |
| #include "obexd/src/manager.h" | |
| #include "obexd/src/mimetype.h" | |
| #include "phonebook.h" | |
| #include "filesystem.h" | |
| struct aparam_header { | |
| uint8_t tag; | |
| uint8_t len; | |
| uint8_t val[0]; | |
| } __attribute__ ((packed)); | |
| #define DID_LEN 18 | |
| struct irmc_session { | |
| struct obex_session *os; | |
| struct apparam_field *params; | |
| uint16_t entries; | |
| GString *buffer; | |
| char sn[DID_LEN]; | |
| char did[DID_LEN]; | |
| char manu[DID_LEN]; | |
| char model[DID_LEN]; | |
| void *request; | |
| }; | |
| #define IRMC_TARGET_SIZE 9 | |
| static const guint8 IRMC_TARGET[IRMC_TARGET_SIZE] = { | |
| 0x49, 0x52, 0x4d, 0x43, 0x2d, 0x53, 0x59, 0x4e, 0x43 }; | |
| /* FIXME: | |
| * the IrMC specs state the first vcard should be the owner | |
| * vcard. As there is no simple way to collect ownerdetails | |
| * just create an empty vcard (which is allowed according to the | |
| * specs). | |
| */ | |
| static const char *owner_vcard = | |
| "BEGIN:VCARD\r\n" | |
| "VERSION:2.1\r\n" | |
| "N:\r\n" | |
| "TEL:\r\n" | |
| "X-IRMX-LUID:0\r\n" | |
| "END:VCARD\r\n"; | |
| static void phonebook_size_result(const char *buffer, size_t bufsize, | |
| int vcards, int missed, | |
| gboolean lastpart, void *user_data) | |
| { | |
| struct irmc_session *irmc = user_data; | |
| DBG("vcards %d", vcards); | |
| irmc->params->maxlistcount = vcards; | |
| if (irmc->request) { | |
| phonebook_req_finalize(irmc->request); | |
| irmc->request = NULL; | |
| } | |
| } | |
| static void query_result(const char *buffer, size_t bufsize, int vcards, | |
| int missed, gboolean lastpart, void *user_data) | |
| { | |
| struct irmc_session *irmc = user_data; | |
| const char *s, *t; | |
| DBG("bufsize %zu vcards %d missed %d", bufsize, vcards, missed); | |
| if (irmc->request) { | |
| phonebook_req_finalize(irmc->request); | |
| irmc->request = NULL; | |
| } | |
| /* first add a 'owner' vcard */ | |
| if (!irmc->buffer) | |
| irmc->buffer = g_string_new(owner_vcard); | |
| else | |
| irmc->buffer = g_string_append(irmc->buffer, owner_vcard); | |
| if (buffer == NULL) | |
| goto done; | |
| /* loop around buffer and add X-IRMC-LUID attribs */ | |
| s = buffer; | |
| while ((t = strstr(s, "UID:")) != NULL) { | |
| /* add up to UID: into buffer */ | |
| irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); | |
| /* | |
| * add UID: line into buffer | |
| * Not sure if UID is still needed if X-IRMC-LUID is there | |
| */ | |
| s = t; | |
| t = strstr(s, "\r\n"); | |
| t += 2; | |
| irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); | |
| /* add X-IRMC-LUID with same number as UID */ | |
| irmc->buffer = g_string_append_len(irmc->buffer, | |
| "X-IRMC-LUID:", 12); | |
| s += 4; /* point to uid number */ | |
| irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); | |
| s = t; | |
| } | |
| /* add remaining bit of buffer */ | |
| irmc->buffer = g_string_append(irmc->buffer, s); | |
| done: | |
| obex_object_set_io_flags(irmc, G_IO_IN, 0); | |
| } | |
| static void *irmc_connect(struct obex_session *os, int *err) | |
| { | |
| struct irmc_session *irmc; | |
| struct apparam_field *param; | |
| int ret; | |
| DBG(""); | |
| manager_register_session(os); | |
| irmc = g_new0(struct irmc_session, 1); | |
| irmc->os = os; | |
| /* FIXME: | |
| * Ideally get capabilities info here and use that to define | |
| * IrMC DID and SN etc parameters. | |
| * For now lets used hostname and some 'random' value | |
| */ | |
| gethostname(irmc->did, DID_LEN); | |
| strncpy(irmc->sn, "12345", sizeof(irmc->sn) - 1); | |
| strncpy(irmc->manu, "obex", sizeof(irmc->manu) - 1); | |
| strncpy(irmc->model, "mymodel", sizeof(irmc->model) - 1); | |
| /* We need to know the number of contact/cal/nt entries | |
| * somewhere so why not do it now. | |
| */ | |
| param = g_new0(struct apparam_field, 1); | |
| param->maxlistcount = 0; /* to count the number of vcards... */ | |
| param->filter = 0x200085; /* UID TEL N VERSION */ | |
| irmc->params = param; | |
| irmc->request = phonebook_pull(PB_CONTACTS, irmc->params, | |
| phonebook_size_result, irmc, err); | |
| ret = phonebook_pull_read(irmc->request); | |
| if (err) | |
| *err = ret; | |
| return irmc; | |
| } | |
| static int irmc_get(struct obex_session *os, void *user_data) | |
| { | |
| struct irmc_session *irmc = user_data; | |
| const char *type = obex_get_type(os); | |
| const char *name = obex_get_name(os); | |
| char *path; | |
| int ret; | |
| DBG("name %s type %s irmc %p", name, type ? type : "NA", irmc); | |
| path = g_strdup(name); | |
| ret = obex_get_stream_start(os, path); | |
| g_free(path); | |
| return ret; | |
| } | |
| static void irmc_disconnect(struct obex_session *os, void *user_data) | |
| { | |
| struct irmc_session *irmc = user_data; | |
| DBG(""); | |
| manager_unregister_session(os); | |
| if (irmc->params) { | |
| if (irmc->params->searchval) | |
| g_free(irmc->params->searchval); | |
| g_free(irmc->params); | |
| } | |
| if (irmc->buffer) | |
| g_string_free(irmc->buffer, TRUE); | |
| g_free(irmc); | |
| } | |
| static int irmc_chkput(struct obex_session *os, void *user_data) | |
| { | |
| DBG(""); | |
| /* Reject all PUTs */ | |
| return -EBADR; | |
| } | |
| static int irmc_open_devinfo(struct irmc_session *irmc) | |
| { | |
| if (!irmc->buffer) | |
| irmc->buffer = g_string_new(""); | |
| g_string_append_printf(irmc->buffer, | |
| "MANU:%s\r\n" | |
| "MOD:%s\r\n" | |
| "SN:%s\r\n" | |
| "IRMC-VERSION:1.1\r\n" | |
| "PB-TYPE-TX:VCARD2.1\r\n" | |
| "PB-TYPE-RX:NONE\r\n" | |
| "CAL-TYPE-TX:NONE\r\n" | |
| "CAL-TYPE-RX:NONE\r\n" | |
| "MSG-TYPE-TX:NONE\r\n" | |
| "MSG-TYPE-RX:NONE\r\n" | |
| "NOTE-TYPE-TX:NONE\r\n" | |
| "NOTE-TYPE-RX:NONE\r\n", | |
| irmc->manu, irmc->model, irmc->sn); | |
| return 0; | |
| } | |
| static int irmc_open_pb(struct irmc_session *irmc) | |
| { | |
| int ret; | |
| /* how can we tell if the vcard count call already finished? */ | |
| irmc->request = phonebook_pull(PB_CONTACTS, irmc->params, | |
| query_result, irmc, &ret); | |
| if (ret < 0) { | |
| DBG("phonebook_pull failed..."); | |
| return ret; | |
| } | |
| ret = phonebook_pull_read(irmc->request); | |
| if (ret < 0) { | |
| DBG("phonebook_pull_read failed..."); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| static int irmc_open_info(struct irmc_session *irmc) | |
| { | |
| if (irmc->buffer == NULL) | |
| irmc->buffer = g_string_new(""); | |
| g_string_printf(irmc->buffer, "Total-Records:%d\r\n" | |
| "Maximum-Records:%d\r\n" | |
| "IEL:2\r\n" | |
| "DID:%s\r\n", | |
| irmc->params->maxlistcount, | |
| irmc->params->maxlistcount, irmc->did); | |
| return 0; | |
| } | |
| static int irmc_open_cc(struct irmc_session *irmc) | |
| { | |
| if (irmc->buffer == NULL) | |
| irmc->buffer = g_string_new(""); | |
| g_string_printf(irmc->buffer, "%d\r\n", irmc->params->maxlistcount); | |
| return 0; | |
| } | |
| static int irmc_open_cal(struct irmc_session *irmc) | |
| { | |
| /* no suport yet. Just return an empty buffer. cal.vcs */ | |
| DBG("unsupported, returning empty buffer"); | |
| if (!irmc->buffer) | |
| irmc->buffer = g_string_new(""); | |
| return 0; | |
| } | |
| static int irmc_open_nt(struct irmc_session *irmc) | |
| { | |
| /* no suport yet. Just return an empty buffer. nt.vnt */ | |
| DBG("unsupported, returning empty buffer"); | |
| if (!irmc->buffer) | |
| irmc->buffer = g_string_new(""); | |
| return 0; | |
| } | |
| static int irmc_open_luid(struct irmc_session *irmc) | |
| { | |
| if (irmc->buffer == NULL) | |
| irmc->buffer = g_string_new(""); | |
| DBG("changelog request, force whole book"); | |
| g_string_printf(irmc->buffer, "SN:%s\r\n" | |
| "DID:%s\r\n" | |
| "Total-Records:%d\r\n" | |
| "Maximum-Records:%d\r\n" | |
| "*\r\n", | |
| irmc->sn, irmc->did, | |
| irmc->params->maxlistcount, | |
| irmc->params->maxlistcount); | |
| return 0; | |
| } | |
| static void *irmc_open(const char *name, int oflag, mode_t mode, void *context, | |
| size_t *size, int *err) | |
| { | |
| struct irmc_session *irmc = context; | |
| int ret = 0; | |
| char *path; | |
| DBG("name %s context %p", name, context); | |
| if (oflag != O_RDONLY) { | |
| ret = -EPERM; | |
| goto fail; | |
| } | |
| if (name == NULL) { | |
| ret = -EBADR; | |
| goto fail; | |
| } | |
| /* Always contains the absolute path */ | |
| if (g_path_is_absolute(name)) | |
| path = g_strdup(name); | |
| else | |
| path = g_build_filename("/", name, NULL); | |
| if (g_str_equal(path, PB_DEVINFO)) | |
| ret = irmc_open_devinfo(irmc); | |
| else if (g_str_equal(path, PB_CONTACTS)) | |
| ret = irmc_open_pb(irmc); | |
| else if (g_str_equal(path, PB_INFO_LOG)) | |
| ret = irmc_open_info(irmc); | |
| else if (g_str_equal(path, PB_CC_LOG)) | |
| ret = irmc_open_cc(irmc); | |
| else if (g_str_has_prefix(path, PB_CALENDAR_FOLDER)) | |
| ret = irmc_open_cal(irmc); | |
| else if (g_str_has_prefix(path, PB_NOTES_FOLDER)) | |
| ret = irmc_open_nt(irmc); | |
| else if (g_str_has_prefix(path, PB_LUID_FOLDER)) | |
| ret = irmc_open_luid(irmc); | |
| else | |
| ret = -EBADR; | |
| g_free(path); | |
| if (ret == 0) | |
| return irmc; | |
| fail: | |
| if (err) | |
| *err = ret; | |
| return NULL; | |
| } | |
| static int irmc_close(void *object) | |
| { | |
| struct irmc_session *irmc = object; | |
| DBG(""); | |
| if (irmc->buffer) { | |
| g_string_free(irmc->buffer, TRUE); | |
| irmc->buffer = NULL; | |
| } | |
| if (irmc->request) { | |
| phonebook_req_finalize(irmc->request); | |
| irmc->request = NULL; | |
| } | |
| return 0; | |
| } | |
| static ssize_t irmc_read(void *object, void *buf, size_t count) | |
| { | |
| struct irmc_session *irmc = object; | |
| int len; | |
| DBG("buffer %p count %zu", irmc->buffer, count); | |
| if (!irmc->buffer) | |
| return -EAGAIN; | |
| len = string_read(irmc->buffer, buf, count); | |
| DBG("returning %d bytes", len); | |
| return len; | |
| } | |
| static struct obex_mime_type_driver irmc_driver = { | |
| .target = IRMC_TARGET, | |
| .target_size = IRMC_TARGET_SIZE, | |
| .open = irmc_open, | |
| .close = irmc_close, | |
| .read = irmc_read, | |
| }; | |
| static struct obex_service_driver irmc = { | |
| .name = "IRMC Sync server", | |
| .service = OBEX_IRMC, | |
| .target = IRMC_TARGET, | |
| .target_size = IRMC_TARGET_SIZE, | |
| .connect = irmc_connect, | |
| .get = irmc_get, | |
| .disconnect = irmc_disconnect, | |
| .chkput = irmc_chkput | |
| }; | |
| static int irmc_init(void) | |
| { | |
| int err; | |
| DBG(""); | |
| err = phonebook_init(); | |
| if (err < 0) | |
| return err; | |
| err = obex_mime_type_driver_register(&irmc_driver); | |
| if (err < 0) | |
| goto fail_mime_irmc; | |
| err = obex_service_driver_register(&irmc); | |
| if (err < 0) | |
| goto fail_irmc_reg; | |
| return 0; | |
| fail_irmc_reg: | |
| obex_mime_type_driver_unregister(&irmc_driver); | |
| fail_mime_irmc: | |
| phonebook_exit(); | |
| return err; | |
| } | |
| static void irmc_exit(void) | |
| { | |
| DBG(""); | |
| obex_service_driver_unregister(&irmc); | |
| obex_mime_type_driver_unregister(&irmc_driver); | |
| phonebook_exit(); | |
| } | |
| OBEX_PLUGIN_DEFINE(irmc, irmc_init, irmc_exit) |