// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2010-2011 Nokia Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "gobex/gobex.h" #include "gobex/gobex-apparam.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/mimetype.h" #include "obexd/src/manager.h" #include "obexd/src/map_ap.h" #include "filesystem.h" #include "messages.h" #define READ_STATUS_REQ 0 #define DELETE_STATUS_REQ 1 #define XML_DECL "" /* Building blocks for x-obex/folder-listing */ #define FL_DTD "" #define FL_BODY_BEGIN "" #define FL_BODY_EMPTY "" #define FL_PARENT_FOLDER_ELEMENT "" #define FL_FOLDER_ELEMENT "" #define FL_BODY_END "" #define ML_BODY_BEGIN "" #define ML_BODY_END "" struct mas_session { struct mas_request *request; void *backend_data; gboolean finished; gboolean nth_call; GString *buffer; GObexApparam *inparams; GObexApparam *outparams; gboolean ap_sent; uint8_t notification_status; }; static const uint8_t MAS_TARGET[TARGET_SIZE] = { 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, 0xdb, 0xb0, 0xde, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 }; static int get_params(struct obex_session *os, struct mas_session *mas) { const uint8_t *buffer; ssize_t size; size = obex_get_apparam(os, &buffer); if (size <= 0) return 0; mas->inparams = g_obex_apparam_decode(buffer, size); if (mas->inparams == NULL) { DBG("Error when parsing parameters!"); return -EBADR; } return 0; } static void reset_request(struct mas_session *mas) { if (mas->buffer) { g_string_free(mas->buffer, TRUE); mas->buffer = NULL; } if (mas->inparams) { g_obex_apparam_free(mas->inparams); mas->inparams = NULL; } if (mas->outparams) { g_obex_apparam_free(mas->outparams); mas->outparams = NULL; } mas->nth_call = FALSE; mas->finished = FALSE; mas->ap_sent = FALSE; } static void mas_clean(struct mas_session *mas) { reset_request(mas); g_free(mas); } static void *mas_connect(struct obex_session *os, int *err) { struct mas_session *mas; DBG(""); mas = g_new0(struct mas_session, 1); *err = messages_connect(&mas->backend_data); if (*err < 0) goto failed; manager_register_session(os); return mas; failed: g_free(mas); return NULL; } static void mas_disconnect(struct obex_session *os, void *user_data) { struct mas_session *mas = user_data; DBG(""); manager_unregister_session(os); messages_disconnect(mas->backend_data); mas_clean(mas); } static int mas_get(struct obex_session *os, void *user_data) { struct mas_session *mas = user_data; const char *type = obex_get_type(os); const char *name = obex_get_name(os); int ret; DBG("GET: name %s type %s mas %p", name, type, mas); if (type == NULL) return -EBADR; ret = get_params(os, mas); if (ret < 0) goto failed; ret = obex_get_stream_start(os, name); if (ret < 0) goto failed; return 0; failed: reset_request(mas); return ret; } static int mas_put(struct obex_session *os, void *user_data) { struct mas_session *mas = user_data; const char *type = obex_get_type(os); const char *name = obex_get_name(os); int ret; DBG("PUT: name %s type %s mas %p", name, type, mas); if (type == NULL) return -EBADR; ret = get_params(os, mas); if (ret < 0) goto failed; ret = obex_put_stream_start(os, name); if (ret < 0) goto failed; return 0; failed: reset_request(mas); return ret; } /* FIXME: Preserve whitespaces */ static void g_string_append_escaped_printf(GString *string, const char *format, ...) { va_list ap; char *escaped; va_start(ap, format); escaped = g_markup_vprintf_escaped(format, ap); g_string_append(string, escaped); g_free(escaped); va_end(ap); } static gchar *get_mse_timestamp(void) { struct timeval time_val; struct tm ltime; gchar *local_ts; char sign; if (gettimeofday(&time_val, NULL) < 0) return NULL; if (!localtime_r(&time_val.tv_sec, <ime)) return NULL; if (difftime(mktime(localtime(&time_val.tv_sec)), mktime(gmtime(&time_val.tv_sec))) < 0) sign = '+'; else sign = '-'; local_ts = g_strdup_printf("%04d%02d%02dT%02d%02d%02d%c%2ld%2ld", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday, ltime.tm_hour, ltime.tm_min, ltime.tm_sec, sign, ltime.tm_gmtoff/3600, (ltime.tm_gmtoff%3600)/60); return local_ts; } static const char *yesorno(gboolean a) { if (a) return "yes"; return "no"; } static void get_messages_listing_cb(void *session, int err, uint16_t size, gboolean newmsg, const struct messages_message *entry, void *user_data) { struct mas_session *mas = user_data; uint16_t max = 1024; gchar *mse_time; if (err < 0 && err != -EAGAIN) { obex_object_set_io_flags(mas, G_IO_ERR, err); return; } if (mas->inparams) g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); if (max == 0) { if (!entry) mas->finished = TRUE; goto proceed; } if (!mas->nth_call) { g_string_append(mas->buffer, ML_BODY_BEGIN); mas->nth_call = TRUE; } if (!entry) { g_string_append(mas->buffer, ML_BODY_END); mas->finished = TRUE; goto proceed; } g_string_append(mas->buffer, "buffer, " handle=\"%s\"", entry->handle); if (entry->mask & PMASK_SUBJECT) g_string_append_escaped_printf(mas->buffer, " subject=\"%s\"", entry->subject); if (entry->mask & PMASK_DATETIME) g_string_append_escaped_printf(mas->buffer, " datetime=\"%s\"", entry->datetime); if (entry->mask & PMASK_SENDER_NAME) g_string_append_escaped_printf(mas->buffer, " sender_name=\"%s\"", entry->sender_name); if (entry->mask & PMASK_SENDER_ADDRESSING) g_string_append_escaped_printf(mas->buffer, " sender_addressing=\"%s\"", entry->sender_addressing); if (entry->mask & PMASK_REPLYTO_ADDRESSING) g_string_append_escaped_printf(mas->buffer, " replyto_addressing=\"%s\"", entry->replyto_addressing); if (entry->mask & PMASK_RECIPIENT_NAME) g_string_append_escaped_printf(mas->buffer, " recipient_name=\"%s\"", entry->recipient_name); if (entry->mask & PMASK_RECIPIENT_ADDRESSING) g_string_append_escaped_printf(mas->buffer, " recipient_addressing=\"%s\"", entry->recipient_addressing); if (entry->mask & PMASK_TYPE) g_string_append_escaped_printf(mas->buffer, " type=\"%s\"", entry->type); if (entry->mask & PMASK_RECEPTION_STATUS) g_string_append_escaped_printf(mas->buffer, " reception_status=\"%s\"", entry->reception_status); if (entry->mask & PMASK_SIZE) g_string_append_escaped_printf(mas->buffer, " size=\"%s\"", entry->size); if (entry->mask & PMASK_ATTACHMENT_SIZE) g_string_append_escaped_printf(mas->buffer, " attachment_size=\"%s\"", entry->attachment_size); if (entry->mask & PMASK_TEXT) g_string_append_escaped_printf(mas->buffer, " text=\"%s\"", yesorno(entry->text)); if (entry->mask & PMASK_READ) g_string_append_escaped_printf(mas->buffer, " read=\"%s\"", yesorno(entry->read)); if (entry->mask & PMASK_SENT) g_string_append_escaped_printf(mas->buffer, " sent=\"%s\"", yesorno(entry->sent)); if (entry->mask & PMASK_PROTECTED) g_string_append_escaped_printf(mas->buffer, " protected=\"%s\"", yesorno(entry->protect)); if (entry->mask & PMASK_PRIORITY) g_string_append_escaped_printf(mas->buffer, " priority=\"%s\"", yesorno(entry->priority)); g_string_append(mas->buffer, "/>\n"); proceed: if (!entry) { mas->outparams = g_obex_apparam_set_uint16(mas->outparams, MAP_AP_MESSAGESLISTINGSIZE, size); mas->outparams = g_obex_apparam_set_uint8(mas->outparams, MAP_AP_NEWMESSAGE, newmsg ? 1 : 0); /* Response to report the local time of MSE */ mse_time = get_mse_timestamp(); if (mse_time) { g_obex_apparam_set_string(mas->outparams, MAP_AP_MSETIME, mse_time); g_free(mse_time); } } if (err != -EAGAIN) obex_object_set_io_flags(mas, G_IO_IN, 0); } static void get_message_cb(void *session, int err, gboolean fmore, const char *chunk, void *user_data) { struct mas_session *mas = user_data; DBG(""); if (err < 0 && err != -EAGAIN) { obex_object_set_io_flags(mas, G_IO_ERR, err); return; } if (!chunk) { mas->finished = TRUE; goto proceed; } g_string_append(mas->buffer, chunk); proceed: if (err != -EAGAIN) obex_object_set_io_flags(mas, G_IO_IN, 0); } static void get_folder_listing_cb(void *session, int err, uint16_t size, const char *name, void *user_data) { struct mas_session *mas = user_data; uint16_t max = 1024; if (err < 0 && err != -EAGAIN) { obex_object_set_io_flags(mas, G_IO_ERR, err); return; } if (mas->inparams) g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); if (max == 0) { if (err != -EAGAIN) mas->outparams = g_obex_apparam_set_uint16( mas->outparams, MAP_AP_FOLDERLISTINGSIZE, size); if (!name) mas->finished = TRUE; goto proceed; } if (!mas->nth_call) { g_string_append(mas->buffer, XML_DECL); g_string_append(mas->buffer, FL_DTD); if (!name) { g_string_append(mas->buffer, FL_BODY_EMPTY); mas->finished = TRUE; goto proceed; } g_string_append(mas->buffer, FL_BODY_BEGIN); mas->nth_call = TRUE; } if (!name) { g_string_append(mas->buffer, FL_BODY_END); mas->finished = TRUE; goto proceed; } if (g_strcmp0(name, "..") == 0) g_string_append(mas->buffer, FL_PARENT_FOLDER_ELEMENT); else g_string_append_escaped_printf(mas->buffer, FL_FOLDER_ELEMENT, name); proceed: if (err != -EAGAIN) obex_object_set_io_flags(mas, G_IO_IN, err); } static void set_status_cb(void *session, int err, void *user_data) { struct mas_session *mas = user_data; DBG(""); mas->finished = TRUE; if (err < 0) obex_object_set_io_flags(mas, G_IO_ERR, err); else obex_object_set_io_flags(mas, G_IO_OUT, 0); } static int mas_setpath(struct obex_session *os, void *user_data) { const char *name; const uint8_t *nonhdr; struct mas_session *mas = user_data; if (obex_get_non_header_data(os, &nonhdr) != 2) { error("Set path failed: flag and constants not found!"); return -EBADR; } name = obex_get_name(os); DBG("SETPATH: name %s nonhdr 0x%x%x", name, nonhdr[0], nonhdr[1]); if ((nonhdr[0] & 0x02) != 0x02) { DBG("Error: requested directory creation"); return -EBADR; } return messages_set_folder(mas->backend_data, name, nonhdr[0] & 0x01); } static void *folder_listing_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; /* 1024 is the default when there was no MaxListCount sent */ uint16_t max = 1024; uint16_t offset = 0; if (oflag != O_RDONLY) { *err = -EBADR; return NULL; } DBG("name = %s", name); if (mas->inparams) { g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); g_obex_apparam_get_uint16(mas->inparams, MAP_AP_STARTOFFSET, &offset); } *err = messages_get_folder_listing(mas->backend_data, name, max, offset, get_folder_listing_cb, mas); mas->buffer = g_string_new(""); if (*err < 0) return NULL; else return mas; } static void *msg_listing_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; struct messages_filter filter = { 0, }; /* 1024 is the default when there was no MaxListCount sent */ uint16_t max = 1024; uint16_t offset = 0; /* If MAP client does not specify the subject length, then subject_len = 0 and subject should be sent unaltered. */ uint8_t subject_len = 0; DBG(""); if (oflag != O_RDONLY) { *err = -EBADR; return NULL; } if (!mas->inparams) goto done; g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); g_obex_apparam_get_uint16(mas->inparams, MAP_AP_STARTOFFSET, &offset); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_SUBJECTLENGTH, &subject_len); g_obex_apparam_get_uint32(mas->inparams, MAP_AP_PARAMETERMASK, &filter.parameter_mask); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERMESSAGETYPE, &filter.type); filter.period_begin = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERPERIODBEGIN); filter.period_end = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERPERIODEND); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERREADSTATUS, &filter.read_status); filter.recipient = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERRECIPIENT); filter.originator = g_obex_apparam_get_string(mas->inparams, MAP_AP_FILTERORIGINATOR); g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERPRIORITY, &filter.priority); done: *err = messages_get_messages_listing(mas->backend_data, name, max, offset, subject_len, &filter, get_messages_listing_cb, mas); mas->buffer = g_string_new(""); if (*err < 0) return NULL; else return mas; } static void *message_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; DBG(""); if (oflag != O_RDONLY) { DBG("Message pushing unsupported"); *err = -ENOSYS; return NULL; } *err = messages_get_message(mas->backend_data, name, 0, get_message_cb, mas); mas->buffer = g_string_new(""); if (*err < 0) return NULL; else return mas; } static void *message_update_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; DBG(""); if (oflag == O_RDONLY) { *err = -EBADR; return NULL; } *err = messages_update_inbox(mas->backend_data, set_status_cb, mas); if (*err < 0) return NULL; else return mas; } static void *message_set_status_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; uint8_t indicator; uint8_t value; DBG(""); if (oflag == O_RDONLY) { *err = -EBADR; return NULL; } if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_STATUSINDICATOR, &indicator)) { *err = -EBADR; return NULL; } if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_STATUSVALUE, &value)) { *err = -EBADR; return NULL; } if (indicator == READ_STATUS_REQ) *err = messages_set_read(mas->backend_data, name, value, set_status_cb, mas); else if (indicator == DELETE_STATUS_REQ) *err = messages_set_delete(mas->backend_data, name, value, set_status_cb, mas); else *err = -EBADR; if (*err < 0) return NULL; return mas; } static ssize_t any_get_next_header(void *object, void *buf, size_t mtu, uint8_t *hi) { struct mas_session *mas = object; DBG(""); if (mas->buffer->len == 0 && !mas->finished) return -EAGAIN; *hi = G_OBEX_HDR_APPARAM; if (mas->ap_sent) return 0; mas->ap_sent = TRUE; if (!mas->outparams) return 0; return g_obex_apparam_encode(mas->outparams, buf, mtu); } static void *any_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { DBG(""); *err = -ENOSYS; return NULL; } static ssize_t any_write(void *object, const void *buf, size_t count) { DBG(""); return count; } static ssize_t any_read(void *obj, void *buf, size_t count) { struct mas_session *mas = obj; ssize_t len; DBG(""); len = string_read(mas->buffer, buf, count); if (len == 0 && !mas->finished) return -EAGAIN; return len; } static int any_close(void *obj) { struct mas_session *mas = obj; DBG(""); if (!mas->finished) messages_abort(mas->backend_data); reset_request(mas); return 0; } static void *notification_registration_open(const char *name, int oflag, mode_t mode, void *driver_data, size_t *size, int *err) { struct mas_session *mas = driver_data; uint8_t status; DBG(""); if (O_RDONLY) { *err = -EBADR; return NULL; } if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_NOTIFICATIONSTATUS, &status)) { *err = -EBADR; return NULL; } mas->notification_status = status; mas->finished = TRUE; *err = 0; return mas; } static struct obex_service_driver mas = { .name = "Message Access server", .service = OBEX_MAS, .target = MAS_TARGET, .target_size = TARGET_SIZE, .connect = mas_connect, .get = mas_get, .put = mas_put, .setpath = mas_setpath, .disconnect = mas_disconnect, }; static struct obex_mime_type_driver mime_map = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = NULL, .open = any_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_message = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/message", .open = message_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_folder_listing = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-obex/folder-listing", .get_next_header = any_get_next_header, .open = folder_listing_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_msg_listing = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-msg-listing", .get_next_header = any_get_next_header, .open = msg_listing_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_notification_registration = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-NotificationRegistration", .open = notification_registration_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_message_status = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/messageStatus", .open = message_set_status_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver mime_message_update = { .target = MAS_TARGET, .target_size = TARGET_SIZE, .mimetype = "x-bt/MAP-messageUpdate", .open = message_update_open, .close = any_close, .read = any_read, .write = any_write, }; static struct obex_mime_type_driver *map_drivers[] = { &mime_map, &mime_message, &mime_folder_listing, &mime_msg_listing, &mime_notification_registration, &mime_message_status, &mime_message_update, NULL }; static int mas_init(void) { int err; int i; err = messages_init(); if (err < 0) return err; for (i = 0; map_drivers[i] != NULL; ++i) { err = obex_mime_type_driver_register(map_drivers[i]); if (err < 0) goto failed; } err = obex_service_driver_register(&mas); if (err < 0) goto failed; return 0; failed: for (--i; i >= 0; --i) obex_mime_type_driver_unregister(map_drivers[i]); messages_exit(); return err; } static void mas_exit(void) { int i; obex_service_driver_unregister(&mas); for (i = 0; map_drivers[i] != NULL; ++i) obex_mime_type_driver_unregister(map_drivers[i]); messages_exit(); } OBEX_PLUGIN_DEFINE(mas, mas_init, mas_exit)