/* * Copyright (C) 2013-2014 Jolla Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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. * */ #include #include #include #include static const char pname[] = "mms-dump"; enum app_ret_value { RET_OK, RET_ERR_CMDLINE, RET_ERR_IO, RET_ERR_DECODE }; #define MMS_DUMP_FLAG_VERBOSE (0x01) #define WSP_QUOTE (127) struct mms_named_value { const char* name; unsigned int value; }; typedef gboolean (*mms_value_decoder)( enum wsp_value_type type, const guint8* val, unsigned int len, unsigned int flags); #define MMS_CONTENT_TYPE "application/vnd.wap.mms-message" #define MMS_WELL_KNOWN_HEADERS(h) \ h(BCC, "Bcc", 0x01, etext )\ h(CC, "Cc", 0x02, etext )\ h(CONTENT_LOCATION, "X-Mms-Content-Location", 0x03, text )\ h(CONTENT_TYPE, "Content-Type", 0x04, unknown )\ h(DATE, "Date", 0x05, date )\ h(DELIVERY_REPORT, "X-Mms-Delivery-Report", 0x06, bool )\ h(DELIVERY_TIME, "X-Mms-Delivery-Time", 0x07, unknown )\ h(EXPIRY, "X-Mms-Expiry", 0x08, expiry )\ h(FROM, "From", 0x09, from )\ h(MESSAGE_CLASS, "X-Mms-Message-Class", 0x0A, mclass )\ h(MESSAGE_ID, "Message-ID", 0x0B, text )\ h(MESSAGE_TYPE, "X-Mms-Message-Type", 0x0C, mtype )\ h(MMS_VERSION, "X-Mms-MMS-Version", 0x0D, version )\ h(MESSAGE_SIZE, "X-Mms-Message-Size", 0x0E, long )\ h(PRIORITY, "X-Mms-Priority", 0x0F, prio )\ h(READ_REPORT, "X-Mms-Read-Report", 0x10, bool )\ h(REPORT_ALLOWED, "X-Mms-Report-Allowed", 0x11, bool )\ h(RESPONSE_STATUS, "X-Mms-Response-Status", 0x12, respstat )\ h(RESPONSE_TEXT, "X-Mms-Response-Text", 0x13, etext )\ h(SENDER_VISIBILITY, "X-Mms-Sender-Visibility", 0x14, short )\ h(STATUS, "X-Mms-Status", 0x15, status )\ h(SUBJECT, "Subject", 0x16, etext )\ h(TO, "To", 0x17, etext )\ h(TRANSACTION_ID, "X-Mms-Transaction-Id", 0x18, text )\ h(RETRIEVE_STATUS, "X-Mms-Retrieve-Status", 0x19, retrieve )\ h(RETRIEVE_TEXT, "X-Mms-Retrieve-Text", 0x1A, etext )\ h(READ_STATUS, "X-Mms-Read-Status", 0x1B, rstatus )\ h(REPLY_CHARGING, "X-Mms-Reply-Charging", 0x1C, short )\ h(REPLY_CHARGING_DEADLINE,"X-Mms-Reply-Charging-Deadline",0x1D, unknown )\ h(REPLY_CHARGING_ID, "X-Mms-Reply-Charging-ID", 0x1E, text )\ h(REPLY_CHARGING_SIZE, "X-Mms-Reply-Charging-Size", 0x1F, long )\ h(PREVIOUSLY_SENT_BY, "X-Mms-Previously-Sent-By", 0x20, unknown )\ h(PREVIOUSLY_SENT_DATE, "X-Mms-Previously-Sent-Date", 0x21, unknown ) #define WSP_WELL_KNOWN_HEADERS(h) \ h(CONTENT_LOCATION, "Content-Location", 0x0E, text )\ h(CONTENT_DISPOSITION, "Content-Disposition", 0x2E, contdisp )\ h(CONTENT_ID, "Content-ID", 0x40, quote )\ h(CONTENT_DISPOSITION2, "Content-Disposition", 0x45, contdisp ) #define MMS_HEADER_ID(id) MMS_HEADER_##id #define MMS_PART_HEADER_ID(id) MMS_PART_HEADER_##id typedef enum mms_header { MMS_HEADER_INVALID, #define h(id,n,x,t) MMS_HEADER_ID(id) = x, MMS_WELL_KNOWN_HEADERS(h) #undef h MMS_HEADER_END, } MMS_HEADER; typedef enum mms_part_header { #define h(id,n,x,t) MMS_PART_HEADER_ID(id) = x, WSP_WELL_KNOWN_HEADERS(h) #undef h } MMS_PART_HEADER; static const char* mms_message_well_known_header_name( int hdr) { static char unknown[6]; switch (hdr) { #define h(id,n,x,t) case MMS_HEADER_ID(id): return n; MMS_WELL_KNOWN_HEADERS(h) #undef h default: snprintf(unknown, sizeof(unknown), "0x%02X", hdr); unknown[sizeof(unknown)-1] = 0; return unknown; } } static const char* mms_part_well_known_header_name( int hdr) { static char unknown[6]; switch (hdr) { #define h(id,n,x,t) case MMS_PART_HEADER_ID(id): return n; WSP_WELL_KNOWN_HEADERS(h) #undef h default: snprintf(unknown, sizeof(unknown), "0x%02X", hdr); unknown[sizeof(unknown)-1] = 0; return unknown; } } static const struct mms_named_value* mms_find_named_value( const struct mms_named_value* values, unsigned int num_values, unsigned int value) { unsigned int i; for (i=0; i 1) { unsigned int i; printf("%02X", val[0]); for (i=1; i *TEXT End-of-string */ if (type == WSP_VALUE_TYPE_TEXT && len > 0 && val[0] == 0x22) { printf("%s", val+1); mms_value_verbose_dump(val, len, flags); return TRUE; } return FALSE; } static gboolean mms_value_decode_enum( enum wsp_value_type type, const guint8* val, unsigned int len, const struct mms_named_value* values, unsigned int num_values, unsigned int flags) { if (type == WSP_VALUE_TYPE_SHORT && len == 1) { const struct mms_named_value* nv; nv = mms_find_named_value(values, num_values, val[0]); if (nv) { printf("%s", nv->name); if (flags & MMS_DUMP_FLAG_VERBOSE) printf(" (%u)", nv->value); return TRUE; } } return mms_value_decode_unknown(type, val, len, flags); } static gboolean mms_value_decode_version( enum wsp_value_type type, const guint8* val, unsigned int len, unsigned int flags) { if (type == WSP_VALUE_TYPE_SHORT && len == 1) { const unsigned int value = val[0]; printf("%u.%u", (val[0] & 0x70) >> 4, val[0] & 0x0f); if (flags & MMS_DUMP_FLAG_VERBOSE) printf(" (%u)", value); return TRUE; } return mms_value_decode_unknown(type, val, len, flags); } static gboolean mms_value_decode_long( enum wsp_value_type type, const guint8* val, unsigned int len, unsigned int flags) { unsigned long long_val; if (type == WSP_VALUE_TYPE_LONG && len <= sizeof(long_val)) { unsigned int i; for (long_val = 0, i=0; iname)) { gsize bytes_in, bytes_out; text = tmp = g_convert((char*)val+consumed, len-consumed, dest_charset, cs->name, &bytes_in, &bytes_out, NULL); } else { if (val[consumed] == WSP_QUOTE) consumed++; if (consumed <= len) text = (char*)val+consumed; } if (text) { printf("%s", text); mms_value_verbose_dump(val, len, flags); g_free(tmp); return TRUE; } } } } return mms_value_decode_unknown(type, val, len, flags); } static gboolean mms_value_decode_from( enum wsp_value_type type, const guint8* val, unsigned int len, unsigned int flags) { /* * Address-present-token Encoded-string-value | Insert-address-token * Address-present-token = * Insert-address-token = */ if (type == WSP_VALUE_TYPE_LONG && len > 0) { if (val[0] == 0x81) { printf(""); if (flags & MMS_DUMP_FLAG_VERBOSE) printf(" (%u)", val[0]); return TRUE; } else if (val[0] == 0x80 && len > 1) { enum wsp_value_type ftype; const void* fval = NULL; unsigned int flen = 0; if (wsp_decode_field(val+1, len-1, &ftype, &fval, &flen, NULL) && mms_value_decode_etext(ftype, fval, flen, 0)) { mms_value_verbose_dump(val, len, flags); return TRUE; } } } return mms_value_decode_unknown(type, val, len, flags); } static gboolean mms_value_decode_expiry( enum wsp_value_type type, const guint8* val, unsigned int len, unsigned int flags) { /* * Absolute-token Date-value | Relative-token Delta-seconds-value * Absolute-token = * Relative-token = */ gboolean ok = FALSE; enum wsp_value_type ftype; const void* fval = NULL; unsigned int flen = 0; if (type == WSP_VALUE_TYPE_LONG && len > 1 && wsp_decode_field(val+1, len-1, &ftype, &fval, &flen, NULL)) { if (val[0] == 0x80 /* Absolute-token */) { ok = mms_value_decode_date(ftype, fval, flen, 0); } else if (val[0] == 0x81 /* Relative-token */) { time_t t; if (ftype == WSP_VALUE_TYPE_LONG && flen > 0 && flen <= sizeof(t)) { unsigned int i; for (t=0, i=0; i * Length-quote = * Length = Uintvar-integer * * Disposition = Form-data | Attachment | Inline | Token-text * Form-data = * Attachment = * Inline = */ static const struct mms_named_value nv_d [] = { { "Form-data", 128 }, { "Attachment", 129 }, { "Inline", 130 } }; if ((type == WSP_VALUE_TYPE_LONG || type == WSP_VALUE_TYPE_SHORT) && len > 0) { const struct mms_named_value* nv; nv = mms_find_named_value(nv_d, G_N_ELEMENTS(nv_d), val[0]); if (nv) { printf("%s", nv->name); mms_value_decode_wsp_params(val + 1, len - 1); mms_value_verbose_dump(val, len, flags); return TRUE; } } return mms_value_decode_unknown(type, val, len, flags); } #define mms_value_decode_short mms_value_decode_unknown #define mms_value_decode_text mms_value_decode_unknown static mms_value_decoder mms_message_value_decoder_for_header( const char* hdr) { #define h(id,n,x,t) if (!strcmp(n,hdr)) return &mms_value_decode_##t; MMS_WELL_KNOWN_HEADERS(h); #undef h return &mms_value_decode_unknown; } static mms_value_decoder mms_part_value_decoder_for_header( const char* hdr) { #define h(id,n,x,t) if (!strcmp(n,hdr)) return &mms_value_decode_##t; WSP_WELL_KNOWN_HEADERS(h); #undef h return &mms_value_decode_unknown; } static gboolean mms_decode_headers( struct wsp_header_iter* iter, const char* prefix, unsigned int flags, const char* (*well_known_header_name)(int hdr), mms_value_decoder (*value_decoder_for_header)(const char* hdr)) { while (wsp_header_iter_next(iter)) { const guint8* hdr = wsp_header_iter_get_hdr(iter); const guint8* val = wsp_header_iter_get_val(iter); unsigned int val_len = wsp_header_iter_get_val_len(iter); mms_value_decoder dec; const char* hdr_name; switch (wsp_header_iter_get_hdr_type(iter)) { case WSP_HEADER_TYPE_WELL_KNOWN: hdr_name = well_known_header_name(hdr[0] & 0x7f); break; case WSP_HEADER_TYPE_APPLICATION: hdr_name = (const char*)hdr; break; default: return FALSE; } printf("%s%s: ", prefix, hdr_name); dec = value_decoder_for_header(hdr_name); if (val_len > 0 && !dec(wsp_header_iter_get_val_type(iter), val, val_len, flags)) { printf("ERROR!\n"); return FALSE; } printf("\n"); } return TRUE; } static gboolean mms_message_decode_headers( struct wsp_header_iter* iter, const char* prefix, unsigned int flags) { return mms_decode_headers(iter, prefix, flags, mms_message_well_known_header_name, mms_message_value_decoder_for_header); } static gboolean mms_part_decode_headers( struct wsp_header_iter* iter, const char* prefix, unsigned int flags) { return mms_decode_headers(iter, prefix, flags, mms_part_well_known_header_name, mms_part_value_decoder_for_header); } static gboolean mms_decode_attachments( struct wsp_header_iter* iter, unsigned int flags) { struct wsp_multipart_iter mi; if (wsp_multipart_iter_init(&mi, iter, NULL, NULL)) { int i; for (i=0; wsp_multipart_iter_next(&mi); i++) { unsigned int n; const void* type; const unsigned char* ct = wsp_multipart_iter_get_content_type(&mi); unsigned int ct_len = wsp_multipart_iter_get_content_type_len(&mi); if (wsp_decode_content_type(ct, ct_len, &type, &n, NULL)) { struct wsp_header_iter hi; const unsigned char* body = wsp_multipart_iter_get_body(&mi); unsigned int len = wsp_multipart_iter_get_body_len(&mi); unsigned int off = body - wsp_header_iter_get_pdu(iter); printf("Attachment #%d:\n", i+1); if (flags & MMS_DUMP_FLAG_VERBOSE) { printf("Offset: %u (0x%x)\n", off, off); printf("Length: %u (0x%x)\n", len, len); } else { printf("Offset: %u\n", off); printf("Length: %u\n", len); } printf(" Content-Type: %s", (char*)type); mms_value_decode_wsp_params(ct + n, ct_len - n); mms_value_verbose_dump(ct, ct_len, flags); printf("\n"); wsp_header_iter_init(&hi, wsp_multipart_iter_get_hdr(&mi), wsp_multipart_iter_get_hdr_len(&mi), 0); if (mms_part_decode_headers(&hi, " ", flags) && wsp_header_iter_at_end(&hi)) { continue; } } return FALSE; } return wsp_multipart_iter_close(&mi, iter); } return FALSE; } static int mms_decode_data( const guint8* data, gsize len, unsigned int flags) { struct wsp_header_iter iter; /* Skip WSP Push notification header if we find one */ if (len >= 3 && data[1] == 6 /* Push PDU */) { unsigned int hdrlen = 0; unsigned int off = 0; const guint8* wsp_data = data+2; gsize wsp_len = len - 2; /* Hdrlen */ if (wsp_decode_uintvar(wsp_data, wsp_len, &hdrlen, &off) && (off + hdrlen) <= wsp_len) { const void* ct = NULL; wsp_data += off; wsp_len -= off; if (wsp_decode_content_type(wsp_data, hdrlen, &ct, &off, NULL) && strcmp(ct, MMS_CONTENT_TYPE) == 0) { printf("WSP header:\n %s\n", (char*)ct); data = wsp_data + hdrlen; len = wsp_len - hdrlen; } } } printf("MMS headers:\n"); wsp_header_iter_init(&iter, data, len, WSP_HEADER_ITER_FLAG_REJECT_CP | WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART); if (mms_message_decode_headers(&iter, " ", flags) && (wsp_header_iter_at_end(&iter) || (wsp_header_iter_is_multipart(&iter) && mms_decode_attachments(&iter, flags) && wsp_header_iter_at_end(&iter)))) { return RET_OK; } else { printf("Decoding FAILED\n"); } return RET_ERR_DECODE; } static int mms_decode_file( const char* fname, unsigned int flags) { int ret = RET_ERR_IO; GError* error = NULL; GMappedFile* map = g_mapped_file_new(fname, FALSE, &error); if (map) { const void* data = g_mapped_file_get_contents(map); const gsize size = g_mapped_file_get_length(map); ret = mms_decode_data(data, size, flags); g_mapped_file_unref(map); } else { printf("%s: %s\n", pname, error->message); g_error_free(error); } return ret; } int main(int argc, char* argv[]) { int ret = RET_ERR_CMDLINE; gboolean ok, verbose = FALSE; GError* error = NULL; GOptionEntry entries[] = { { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Enable verbose output", NULL }, { NULL } }; GOptionContext* options = g_option_context_new("FILES"); g_option_context_add_main_entries(options, entries, NULL); ok = g_option_context_parse(options, &argc, &argv, &error); if (ok) { if (argc > 1) { int i, flags = 0; if (verbose) flags |= MMS_DUMP_FLAG_VERBOSE; for (i=1; i 2) printf("\n%s\n\n", fname); ret = mms_decode_file(fname, flags); if (ret != RET_OK) break; } } else { char* help = g_option_context_get_help(options, TRUE, NULL); printf("%s", help); g_free(help); } } else { printf("%s: %s\n", pname, error->message); g_error_free(error); } g_option_context_free(options); return ret; } /* * Local Variables: * mode: C * c-basic-offset: 4 * indent-tabs-mode: nil * End: */