pax_global_header00006660000000000000000000000064145637445410014527gustar00rootroot0000000000000052 comment=81956ad0c3786d32c1c7f7d897dddd1bc3a9334b mmsd-2.6.0/000077500000000000000000000000001456374454100124745ustar00rootroot00000000000000mmsd-2.6.0/.editorconfig000066400000000000000000000002541456374454100151520ustar00rootroot00000000000000root = true [*.{c,h}] end_of_line = lf insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true indent_style = tab indent_size = 8 max_line_length = 80 mmsd-2.6.0/.gitignore000066400000000000000000000000171456374454100144620ustar00rootroot00000000000000_build/ .cache mmsd-2.6.0/.gitlab-ci.yml000066400000000000000000000023421456374454100151310ustar00rootroot00000000000000--- image: alpine:edge .only-default: &only-default only: - master - merge_requests - tags build-alpine: <<: *only-default before_script: - apk -q add build-base c-ares-dev dbus-dev libphonenumber-dev glib-dev libsoup3-dev json-c-dev meson modemmanager-dev pkgconf mobile-broadband-provider-info script: # check for uninitialized g_autoptr, which cause segfaults in musl - grep -n -e 'g_autoptr(\w*)\s*\w*;' -R . && exit 1 - meson -Dbuild-mmsctl=true -Dwerror=true _build - meson compile -C _build - meson test -C _build --print-errorlogs artifacts: paths: - _build/ build-debian: image: debian:bookworm <<: *only-default before_script: - apt update - apt install -y debhelper-compat meson libdbus-1-dev libglib2.0-dev libjson-c-dev libphonenumber-dev libmm-glib-dev libc-ares-dev libsoup-3.0-dev mobile-broadband-provider-info script: - meson -Dbuild-mmsctl=true -Dwerror=true _build - meson compile -C _build - meson test -C _build --print-errorlogs artifacts: paths: - _build/ mmsd-2.6.0/AUTHORS000066400000000000000000000021531456374454100135450ustar00rootroot00000000000000mmsd-tng Authors Chris Talbot Clayton Craft Parts adapted from: https://source.puri.sm/Librem5/purple-mm-sms https://gitlab.gnome.org/GNOME/evolution-data-server/-/blob/master/src/addressbook/libebook-contacts/ https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/core/devices/wwan/nm-service-providers.c Contributors: anteater Arnaud Ferraris Elias Rudberg Evangelos Ribeiro Tzaras Debian on Mobile Team Kyle Evans kent Mohammad Sadiq Guido Günther Travis Wrightsman Julian Samaroo Tristan Mahe Tim Hollabaugh Antoine Mercadal Andrey Skvortsov mmsd Authors Marcel Holtmann Denis Kenzior Sébastien Bianti Christophe Guiraud Regis Merlino Ronald Tessier Jens Rehsack NOTE: If there is an error and you feel that your name should be included, my apoligies, as this was an honest mistake. Please send me an email or an MR asking for your inclusion. mmsd-2.6.0/COPYING000066400000000000000000000431261456374454100135350ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. mmsd-2.6.0/ChangeLog000066400000000000000000000137621456374454100142570ustar00rootroot00000000000000mmsd-tng (2.6.0) [ Chris Talbot ] * Fix a bunch of compiler warnings * main: check `~/.mms` instead of `~` for R/W access mmsd-tng (2.5.0) [ Chris Talbot ] * Prioritize new received and sent messaged in queue * Make messages fail sending if more than 3 hours old mmsd-tng (2.4.0) [ Chris Talbot ] * Account for short codes * Add option to pull max MMS size from m-b-p-i * Make checking APNs case insensitive mmsd-tng (2.3.0) [ Chris Talbot ] * Add option to set Modem number is it is not known. mmsd-tng (2.2.0) [ Chris Talbot ] * Bug Fixes and better logging support mmsd-tng (2.1.0) [ Chris Talbot ] * Fake Android's User Agent * Do not start if the home directory isn't R/W [Aren M] * Fix segfault in mmsctl mmsd-tng (2.0.0) Big Thanks to David Haltinner for help testing! [ Chris Talbot ] * Fix Resolver Proxy * Ensure Content Type is set mmsd-tng (2.0~beta) This verion now requires libsoup3 and is incompatible with libsoup2. If you still need libsoup2, look here: https://gitlab.com/kop316/mmsd/-/tree/1.x [ Chris Talbot ] * Migrate from libsoup2 to libsoup3 mmsd-tng (1.12.1) [ Chris Talbot ] * HOTFIX: Fix Double free issue in VZW * Fix memory leak mmsd-tng (1.12) [ Chris Talbot ] * Automatically save/restore old MMS settings * Do not reset settings on bad modem loads * Make some functions more resilient mmsd-tng (1.11) [ Chris Talbot ] * Fix various memory leaks * Improve how service handles requeued messages mmsd-tng (1.10) [ Andrey Skvortsov ] * Do not emit certain subjects that are automatically added [ Antoine Mercadal ] * Fix a couple memory issues [ Chris Talbot ] * Update Documentation * Default to C-ares for DNS mmsd-tng (1.9) [ Antoine Mercadal ] * Use DNS settings from ModemManager for C-ares mmsd-tng (1.8) [ Chris Talbot ] * Fix various memory leaks * Add dbus signal for issues sending/receiving MMSes * Work around issue in Modem Manager if it cannot delete SMS WAP * Don't make a request for failed decoded MMS [ Tim Hollabaugh ] * Fix mms proxy issue with AT&T mmsd-tng (1.7) [ Chris Talbot ] * Don't try to set modem state * Add better network handling for MMS * Add support for expired MMSes [ Mohammed Sadiq ] * Add "ChangeAllSettings" Method Call * Refactor changing settings via d-bus [ Guido Günther ] * Fix logging mmsd-tng (1.6) [ Chris Talbot ] * Fix various small bugs [ Gled ] * Add support for receiving Verizon USA MMS [ Guido Günther ] * Fix various small bugs mmsd-tng (1.5) [ Chris Talbot ] * Reset Settings if IMSI changes * Fix Memory Leaks * Add Headers to be compatible with VZW USA [ Julian Samaroo ] * Add frontend CLI mmsd-tng (1.4) [ Chris Talbot ] * Fix several memory leaks * (I hope) Fix segfault issue when sending MMS * Remove stale MMS requests * Remove ofono plugin mmsd-tng (1.3) [ Chris Talbot ] * Add support for mobile-broadband-provider-info * Sync settings to settings file as soon as they are changed mmsd-tng (1.2) [ Chris Talbot ] * Depend on libphonenumber instead of libebook-contacts * service: filter out content type on sent messages mmsd-tng (1.1.1) [ Chris Talbot ] * Initialize auto-pointer to NULL mmsd-tng (1.1) [ Chris Talbot ] * Fix various memory leaks * Do not display sensitive information in debug logs * Work around Android/iOS contact card and calendar bug * Fix dbus interface bug * Remove depreciated nodaemon option * Remove depreciated modemmanager settings code * Remove sleep () from code [ Travis Wrightsman ] * Add option to use systemd-resolved to retrieve mmsd-tng (1.0) [ Chris Talbot ] * Fix verious bugs [ Evangelos Ribeiro Tzaras ] * Fix various memory leaks mmsd-tng (1.0~beta5) [ Chris Talbot ] * Add support for delivery reports [ Guido Gunther ] * Fix various Memory Leaks mmsd-tng (1.0~beta4) [ Chris Talbot ] * Fix bug in "ChangeProperty" signal * Clean up Copyright Headers mmsd-tng (1.0~beta3) [ Chris Talbot ] * Add Modem's own NUmber in "AddMessage" Signal * Fix Various issues with libsoup implimentation [ Clayton Craft ] * Fix Various issues with libsoup implimentation mmsd-tng (1.0~beta2) [ Chris Talbot ] * Add option to include Subject in Sent Messages [ Clayton Craft ] * Replace gweb with libsoup * Fix memory leaks mmsd-tng (1.0~beta1) [ Chris Talbot ] * Make Maximum Attachments a User Defineable Value * Delete Daemon Option * Fix Generate UUID for new MMSes * Update GetProperties and SetProperty in Modem Manager Plugin * Integrate Modem Manager Settings into Main Settings file * Format all numbers to E164 * Add option to create SMIL for MMSes * MM: Add feature to choose default modem if you have multiple modems. mmsd-tng (1.0~beta0) UNRELEASED; urgency=medium [ Chris Talbot ] * Delete dbus customer functionality * Update dbus 1.0 to GIO dbus * Disable Ofono Plugin pending update to GIO dbus mmsd-tng (0.3) UNRELEASED; urgency=medium [ Clayton Craft ] * Ensure compatibility with muslc mmsd-tng (0.2) UNRELEASED; urgency=medium [ Chris Talbot ] * Fix issue if MMS attachment does not have a filename * Refactor debug messages * Fix intentation of modemmanager.c to match upstream style * Add check before g_memdup() to prevent possible buffer overflow [ Clayton Craft ] * Replace autotools with meson * Fix several compiler warnings mmsd-tng (0.1-4) [ Chris T ] * Format modemmanager.c to match upstream style * Support Autoprocessing SMS WAPs [ Clayton Craft ] * Replace Autotools with Meson mmsd-tng (0.1-3) UNRELEASED; urgency=medium [ Chris T ] * Add ModemManager Support * Fix multiple date/time bugs in MMS messages * Fix support for Telus Canada [ Anteater ] * Fix mmsd-tng to work with T-Mobile [ Kent ] * Add Support for AT&T [ Elias Rudberg ] * Fix issue if encoded text has length of 0 mmsd (0.0~git20190724-1) UNRELEASED; urgency=medium [ Chris T ] * Synced with mmsd upstream mmsd-2.6.0/README000066400000000000000000000140711456374454100133570ustar00rootroot00000000000000Multimedia Messaging Service Daemon - The Next Generation (mmsd-tng) **************************** Copyright (C) 2010-2011, Intel Corporation 2021, Chris Talbot About =========================== mmsd-tng is a lower level daemon that transmits and recieves MMSes. It works with both the Modem Manager stack. Modem Manager specific notes =========================== Upon start up, mmsd-tng looks for and tracks the state of the modem that has Modem Messaging (i.e. SMS) capabilities. Since mmsd-tng is a lower level program, mmsd-tng assumes that other parts of the OS stack manage mobile connectivity. Please note that due to limitations of Modem Manager, mmsd-tng does not support having multiple APNs at the same time (for carriers that seperate MMS APN from Mobile Data APNs). Please read "Configuring the Modem Manager Plugin" for configuration. Compiling mmsd-tng ============================ In order to compile proxy daemon you need following software packages: - GCC compiler - D-Bus library - GLib library - Modem Manager Library - LibSoup library - c-ares DNS resolver library Installing mmsd-tng ============================ Build tools requirements When building and testing directly from the repository it is important to have at least meson version 0.56 or later installed. In addition, some supported Meson backend is required (e.g. ninja). To prepare/configure the project for compilation: # meson _build To build the project: # meson compile -C _build Run tests: # meson test -C _build Install: # meson install -C _build Running from within the source code repository ============================================== Meson will build the binary in builddir (_build, using the examples above), and can be invoked directly. Run with debugging # ./_build/mmsdtng -d General Configuration =========================== On first run, mmsd-tng will write a settings file at "$HOME/.mms/$PLUGIN/mms" IMPORTANT NOTE: If you change settings in this file, mmsd-tng MUST NOT BE RUNNING in order for your settings to take effect! This settings file use sane defaults, but you can change them: UseDeliveryReports Whether you want delivery reports for MMSes you send AutoCreateSMIL mmsd-tng will attempt to create SMIL for any outgoing MMS without SMIL TotalMaxAttachmentSize The maximum size all of your attachments can be before mmsd-tng rejects it. This value is in bytes. NOTE: This value is carrier specific! Changing this value to a higher number may cause your carrier to silently reject MMSes you send. CHANGE AT YOUR OWN RISK! MaxAttachments The maximum number of attachments allowed before mmsd-tng rejects it. NOTE: This value is carrier specific! Changing this value to a higher number may cause your carrier to silently reject MMSes you send. CHANGE AT YOUR OWN RISK! ForceCAres Force the C-Ares backend even if systemd is present NOTE: This value can only be changed via manual file editting, and only while mmsd-tng is not running. Configuring the Modem Manager Plugin =========================== On first run, mmsd-tng will write a settings file at "$HOME/.mms/$PLUGIN/mms" It will then attempt to look up your carrier settings from: https://gitlab.gnome.org/GNOME/mobile-broadband-provider-info If it cannot find it, I highly suggest for you to add your carrrier settings here. IMPORTANT NOTE: If you change any settings through the file, mmsd-tng MUST NOT BE RUNNING for the changes to take effect! You can change CarrierMMSC, CarrierMMSProxy, or MMS_APN via dbus and they will take effect right away, but any messages sent to the mmsd-tng queue need to be processed again. The easiest way to do this is to reset mmsd-tng. But it can be done with the dbus proxy call ProcessMessageQueue(). This settings file needs to be changed before mmsd-tng will connect! The settings are as follows: CarrierMMSC Get this from your carrier. Carrier MMSC Format: "http://mms.example.com" CarrierMMSProxy Get this from your carrier. MMS Proxy Format: "proxy.example.com:80", "proxy.example.com", or "NULL" In the example, the proxy hostname is "proxy.example.com" and the proxy port is "80" If you do not set the proxy port (e.g. use "proxy.example.com" ), mmsd will default to port 80. If you do NOT have a proxy, set this to "NULL" DefaultModemNumber If you have multiple modems, this will allow you to only choose which modem to connect to by reading its number. The number MUST be set to E.164 Setting this to NULL means mmsd-tng will connect to the first modem it finds. Unless you have multiple modems (not common), you can leave this set to "NULL" MMS_APN Note that at this point, this plugin can only support one bearer at a time (this works fine for carriers with a combined Internet/MMS APN but will not function with carriers that have two APNS seperating the two) MMS APN Format: "apn.example.com" AutoProcessOnConnection Tell mmsd-tng to automatically send and recieve messages when the modem is connected. This will also allow mmsd-tng to auto send/recieve if the modem is disconnected and reconnects, suspends and unsuspends, etc. AutoProcessOnConnection Options: "true" or "false" AutoProcessSMSWAP Tell mmsd-tng to automatically check and process SMS WAPs. This can be useful if you do not have a chat application that manages MMS or if a higher level chat application does not process SMS WAPs to send to mmsd-tng. AutoProcessSMSWAP Options: "true" or "false" An example of what you are looking for is here: https://www.t-mobile.com/support/devices/not-sold-by-t-mobile/byod-t-mobile-data-and-apn-settings From this: CarrierMMSC=http://mms.msg.eng.t-mobile.com/mms/wapenc MMS_APN=fast.t-mobile.com CarrierMMSProxy=NULL mmsd-2.6.0/config.h.in000066400000000000000000000032261456374454100145220ustar00rootroot00000000000000/* Define to 1 if you have the header file. */ #mesondefine HAVE_DLFCN_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_INTTYPES_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_RESOLV_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STDINT_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STDIO_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STRING_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_UNISTD_H /* Name of package */ #define PACKAGE "@NAME@" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "@NAME@ @VERSION@" /* Define to the version of this package. */ #define PACKAGE_VERSION "@VERSION@" /* Define to 1 if all of the C90 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #mesondefine STDC_HEADERS /* Version number of package */ #define VERSION "@VERSION@" /* Location of the Mobile Broadband provider info */ #define MOBILE_BROADBAND_PROVIDER_INFO_DATABASE "@MOBILE_BROADBAND_PROVIDER_INFO_DATABASE@" /* Build source root */ #define SOURCE_ROOT "@SOURCE_ROOT@" mmsd-2.6.0/doc/000077500000000000000000000000001456374454100132415ustar00rootroot00000000000000mmsd-2.6.0/doc/architecture.txt000066400000000000000000000025261456374454100164710ustar00rootroot00000000000000Architecture overview ===================== The MMS daemon implements the Multimedia Message Service with integration into the oFono and Modem Manager telephony stack. +-----------------------+ | Message application | +-----------------------+ ^ | Session D-Bus | +------------------------------------------------+ | | | MMS daemon | | | +------------------------------------------------+ | | | Modem Manager / oFono plugin | | | +------------------------------------------------+ ^ | System D-Bus | +------------------------------------------------+ | | | Modem Manager / oFono telephony stack | | | +------------------------------------------------+ mmsd-2.6.0/doc/manager-api.txt000066400000000000000000000015521456374454100161660ustar00rootroot00000000000000Manager hierarchy ================= Service org.ofono.mms Interface org.ofono.mms.Manager Object path /org/ofono/mms Methods array{object,dict} GetServices() Get an array of service objects and properties that represents the currently available services. This method call should only be used once when an application starts up. Further service additions and removal shall be monitored via ServiceAdded and ServiceRemoved signals. Possible Errors: [service].Error.InvalidArguments Signals ServiceAdded(object path, dict properties) Signal that is sent when a new service is added. It contains the object path of new service and also its properties. ServiceRemoved(object path) Signal that is sent when a service has been removed. The object path is no longer accessible after this signal and only emitted for reference. mmsd-2.6.0/doc/message-api.txt000066400000000000000000000042101456374454100161720ustar00rootroot00000000000000Message hierarchy ================= Service org.ofono.mms Interface org.ofono.mms.Message Object path [variable prefix]/{message0,message1,...} Methods void MarkRead() Change the status of an incoming message to read. This is only supported for incoming messages and is meant as user interface driven action. Possible Errors: [service].Error.InvalidArguments void Delete() Delete this messages from storage. When deleting a message a MessageRemoved signal will be triggered. Possible Errors: [service].Error.InvalidArguments Signals PropertyChanged(string name, variant value) This signal indicates a changed value of the given property. The only expected property change is for the message status. Properties string Status [readonly] The status of the message. Possible values are "received", "read", "sent", "sending_failed", and "draft". string Date [readonly] The date of the message. Either when it was received or sent. string Subject [readonly, optional] Contains the optional subject of a MMS. string Sender [readonly, optional] Number of sender. gboolean Delivery Report [readonly] Whether the message is requesting a delivery report string Delivery Status [readonly, optional] A CSV of each phone number in the format of "+12345550200=none" where "+12345550200" is the phone number and "none" is the delivery status. This will only be present if "Delivery Report" is true. NOTE: If this is a "PropertyChanged" signal, it will be in the format of "delivery_update,+12345550200=none" string Modem Number [readonly] The modem number associated with the MMS service. While it will be with every MMS, this is meant to make it easier to seperate out one's own number from the recipients in a group MMS. array{string} Recipients [readonly] Numbers of recipients. string Smil [readonly, optional] SMIL body in UTF-8 format. array{string id, string content-type, string filename, uint64 offset, uint64 len} Attachments [readonly] List of attachement content types, identifiers, offset and length of data in the stored file. mmsd-2.6.0/doc/modem-manager-api.txt000066400000000000000000000061131456374454100172630ustar00rootroot00000000000000Message hierarchy ================= Service org.ofono.mms Interface org.ofono.mms.ModemManager Object path /org/ofono/mms Methods PushNotify() Gvariant input Format String (ay) This is the data contents of the SMS WAP ChangeSettings() Change a setting in the Modem Manager Plugin IMPORTANT NOTE: Settings changed here will work right away, but any messages sent to the mmsd-tng queue need to be processed again. The easiest way to do this is to reset mmsd-tng. But it can be done with the dbus proxy call ProcessMessageQueue() Gvariant Input Format String ((sv)) ViewSettings() Change all Modem Manager related settings Gvariant Output Format String (a{sv}) NOTE: The Settings could be called when the Modem is not ready. In this case, the `ModemNumber` setting is set to "Modem Not Ready" ChangeAllSettings() Change all Modem Manager related settings at the same time Gvariant Input Format String (a{sv}) ProcessMessageQueue() This manually activates the Modem Manager Bearer to process any messages not sent or received yet. The primary idea behind is two fold: a) If the Bearer Handler is misconfigured, the OS/higher level program can change the settings via the dbus and test the bearer handler to confirm it works. b) If modem data is disconnected (manually or due to modem suspend), the OS/higher level program can also track this and can command mmsd-tng to now process any messages it needs to send/recieve once modem data is active. Since BearerHandlerError() emits a signal for any errors activating the modem contect, ProcessMessageQueue() does not return any value. Signals BearerHandlerError() If the bearer handler has an issue activating the context, it will emit a signal of the error. The errors are shown above. NOTE: MMSD_MM_MODEM_CONTEXT_ACTIVE will never be emitted as a signal. Gvariant Output Format String (h) enum { MMSD_MM_MODEM_MMSC_MISCONFIGURED, //the MMSC is the default value MMSD_MM_MODEM_NO_BEARERS_ACTIVE, //The Modem has no bearers MMSD_MM_MODEM_INTERFACE_DISCONNECTED, //mmsd-tng found the right bearer, but it is disconnected MMSD_MM_MODEM_INCORRECT_APN_CONNECTED, //no APN is connected that matches the settings MMSD_MM_MODEM_FUTURE_CASE_DISCONNECTED, //Reserved for future case MMSD_MM_MODEM_CONTEXT_ACTIVE //No error, context activated properly } mm_context_connection; SettingsChanged() mmsd-tng changed the MMS settings based on an auto configureation Gvariant Output Format String (sss) apn: The MMS APN (may be identical to the internet APN based on carrier settings) mmsc: The MMSC proxy: The MMS Proxy (if there is none, it will be sent as "NULL") Properties None mmsd-2.6.0/doc/service-api.txt000066400000000000000000000156451456374454100162240ustar00rootroot00000000000000Service hierarchy ================= Service org.ofono.mms Interface org.ofono.mms.Service Object path [variable prefix]/{service0,service1,...} Methods array{object,dict} GetMessages() Get an array of message objects and properties that represents the currently received and sent messages. This method call should only be used once when an service becomes available. Further message additions and removal shall be monitored via MessageAdded and MessageRemoved signals. Possible Errors: [service].Error.InvalidArguments dict GetProperties() Returns properties for the manager object. See the properties section for available properties. Possible Errors: [service].Error.NotImplemented object SendMessage(array{string} recipients, variant smil, array{string id, string content-type, string filename}) OR object SendMessage(array{string} recipients, array{dict, value}, array{string id, string content-type, string filename}) Send a new message to recipients with SMIL body and list of attachement content types, identifiers and filenames. The SMIL body is required to be in UTF-8 format. On success a message object will be created and also signaled by MessageAdded signal. The initial status of the message is a draft and and will be updated once sending has succeeded. array{dict, value} Dict Options: DeliveryReport (True/False): Override Default Delivery report option Subject (string): Add a Subject to the MMS smil (string): Add smil to the MMS Possible Errors: [service].Error.InvalidArguments [service].Error.UnsupportedMessage [service].Error.TransientFailure [service].Error.TransientNetworkProblem [service].Error.PermanentFailure [service].Error.PermanentServiceDenied [service].Error.PermanentMessageFormatCorrupt [service].Error.PermanentInvalidAddress [service].Error.PermanentContentNotAccepted [service].Error.PermanentLackOfPrepaid void SetProperty(string name, variant value) Changes the value of the specified property. Possible Errors: [service].Error.InvalidArguments Signals MessageAdded(object path, dict properties) Signal that is sent when a new message is added. It contains the object path of new message, its properties. MessageRemoved(object path) Signal that is sent when a message has been removed. The object path is no longer accessible after this signal and only emitted for reference. MessageSendError(a{sv}: "ErrorType": uint32 error_type, "Host": string host, "MessagePath": string message_path) Signal that is sent when there is a transmit error with an MMS. Note that this may expand in the future error type: enum mms_tx_rx_error { MMS_TX_RX_ERROR_UNKNOWN, MMS_TX_RX_ERROR_DNS, MMS_TX_RX_ERROR_HTTP }; host: the host the MMS was trying to contact message_path: The dbus path of the MMS message MessageReceiveError(a{sv}: "ErrorType": uint32 error_type, "Host": string host, "From": string from) Signal that is sent when there is a transmit error with an MMS. Note that this may expand in the future error type: enum mms_tx_rx_error { MMS_TX_RX_ERROR_UNKNOWN, MMS_TX_RX_ERROR_DNS, MMS_TX_RX_ERROR_HTTP }; host: the host the MMS was trying to contact from: who the MMS is from if being received (in e164 format) Properties boolean UseDeliveryReports This property controls whether MMS Status Reports, sometimes better known as Delivery Reports are to be used. If enabled, all outgoing MMS messages will be flagged to request a status report from the MMSC. boolean AutoCreateSMIL This property controls whether mmsd-tng makes the smil automcatically for you or if you wish to feed it manually. integer TotalMaxAttachmentSize The maximum size all of your attachments can be before mmsd-tng rejects it. This value is in bytes. NOTE: This value is carrier specific! Changing this value to a higher number may cause your carrier to silently reject MMSes you send. CHANGE AT YOUR OWN RISK! integer MaxAttachments The maximum number of attachments allowed before mmsd-tng rejects it. NOTE: This value is carrier specific! Changing this value to a higher number may cause your carrier to silently reject MMSes you send. CHANGE AT YOUR OWN RISK! integer NotificationInds The number of notification indications that mmsd-tng has. This means mmsd-tng has MMSes it has not downloaded yet. mmsd-2.6.0/doc/standards.txt000066400000000000000000000022601456374454100157650ustar00rootroot00000000000000Referencing standards in the source =================================== When referencing standard documents use raw numbers xx.xxx for 3GPP documents or xxx.xxx for ETSI document (eg: 23.040). If needing to point to an specific section/subsection, explicitly say "Section foo" 3GPP specs can be found in http://3gpp.org/ftp/Specs. For documents from OMA or WAP Forum use the raw numbers with its prefix WAP-xxx (eg. WAP-230). If needed use also the abbreviation within the reference (eg. WAP-230-WSP). WAP Forum specs can be found in http://www.wapforum.org/what/technical.htm or in http://www.openmobilealliance.org/Technical/wapindex.aspx for the new Open Mobile Alliance (OMA) site. WAP Forum Specification ======================= - WAP-230-WSP: Wireless Session Protocol Specification Describes the WSP encoding that is used for MMS and WAP Push messages. - WAP-209-MMSEncapsulation: MMS Encapsulation Protocol Describes the encapsulation of MMS messages using WSP. It also describes the usage of HTTP as transport and MIME multipart for message content. WAP Assigned Names or Numbers ============================= - WSP Content Type http://www.wapforum.org/wina/wsp-content-type.htm mmsd-2.6.0/doc/storage.txt000066400000000000000000000053601456374454100154520ustar00rootroot00000000000000mmsd storage design ******************* The mmsd persists the mms messages on the file system in a directory named ".mms/" under the user home directory. (e.g: /home//.mms/modemmanager/ -> "modemmanager" is the service identifier) A mms message is stored in its raw PDU format in a file named with a generated (e.g: 126588fe14db814b781803a17e99b45b0d48). Another file with the same prefix, named .status (e.g: 126588fe14db814b781803a17e99b45b0d48.status) contains meta information related to the raw PDU. Meta file Example ================= [info] read=false state=notification id=0123456789ABCDEF date=2021-05-21T10:20:48-0400 Meta file Keys/Values details ============================= date: Date and time Message was sent or received. read: The message local "Read" status, possible values: "true" or "false". state: The message local state, possible values can be: - "notification": m-Notify.Ind PDU not yet downloaded. - "downloaded": m-Retrieve.Conf PDU downloaded, but not yet acknowledged. - "received": m-Retrieve.Conf PDU downloaded and successfully acknowledged. - "draft": m-Send.Req PDU ready for sending. - "sent": m-Send.Req PDU successfully sent. id: this is the value provided in the M-Send.conf PDU (assigned by MMSC in response to a M-Send.req message), this entry will only be created upon M-Send.conf message reception if the delivery report was requested. For sent messages, a group [delivery_status] could take place in addition to [info] if delivery report is requested. It will be used to manage the received delivery_report sent by each recipients. This group will have an entry per recipient, the associated value will be set to "none" (which means no report has been received yet) and updated upon report reception. The stored "id" (provided by the MMSC in the Send.conf msg) must match the received "id" in the delivery.ind push msg sent by each recipients. In this group, every recipient has a MMS Delivery status value which can be one of the following: - "none": no report has been received yet. - "expired": recipient did not retrieve the MMS before expiration. - "retrieved": MMS successfully retrieved by the recipient. - "rejected": recipient rejected the MMS. - "deferred": recipient decided to retrieve the MMS at a later time. - "indeterminate": cannot determine if the MMS reached its destination. - "forwarded": recipient forwarded the MMS without retrieving it first. - "unreachable": recipient is not reachable. Example of a sent_message meta file with delivery report requested ================================================================== [info] state=sent id=0123456789ABCDEF [delivery_status] +33612345678=retrieved +4915187654321=none mmsd-2.6.0/meson.build000066400000000000000000000067641456374454100146530ustar00rootroot00000000000000project( 'mmsdtng', 'c', 'cpp', version : '2.6.0', meson_version : '>= 0.56.0', ) check_headers = [ ['HAVE_DLFCN_H', 'dlfcn.h'], ['HAVE_INTTYPES_H', 'inttypes.h'], ['HAVE_STDINT_H', 'stdint.h'], ['HAVE_STDIO_H', 'stdio.h'], ['HAVE_STDLIB_H', 'stdlib.h'], ['HAVE_STRINGS_H', 'strings.h'], ['HAVE_STRING_H', 'string.h'], ['HAVE_SYS_STAT_H', 'sys/stat.h'], ['HAVE_SYS_TYPES_H', 'sys/types.h'], ['HAVE_UNISTD_H', 'unistd.h'] ] cc = meson.get_compiler('c') conf_data = configuration_data() foreach h : check_headers if cc.has_header(h.get(1)) conf_data.set(h.get(0), 1) endif endforeach conf_data.set('VERSION', meson.project_version()) conf_data.set('NAME', meson.project_name()) mobile_broadband_provider_info_database = dependency('mobile-broadband-provider-info').get_variable(pkgconfig: 'database') conf_data.set('MOBILE_BROADBAND_PROVIDER_INFO_DATABASE', mobile_broadband_provider_info_database) conf_data.set('SOURCE_ROOT', meson.project_source_root()) conf = configure_file( input : 'config.h.in', output : 'config.h', configuration : conf_data ) add_project_arguments('-DMMS_PLUGIN_BUILTIN', language : 'c') add_project_arguments('-DHAVE_CONFIG_H', language : 'c') add_project_arguments('-DPLUGINDIR="@0@/mms/plugins"'.format(get_option('libdir')), language : 'c') project_c_args = [] test_c_args = [ '-Wcast-align', '-Wdeclaration-after-statement', '-Werror=address', '-Werror=array-bounds', '-Werror=empty-body', '-Werror=implicit', '-Werror=implicit-function-declaration', '-Werror=incompatible-pointer-types', '-Werror=init-self', '-Werror=int-conversion', '-Werror=int-to-pointer-cast', '-Werror=main', '-Werror=misleading-indentation', '-Werror=missing-braces', '-Werror=missing-include-dirs', '-Werror=nonnull', '-Werror=overflow', '-Werror=parenthesis', '-Werror=pointer-arith', '-Werror=pointer-to-int-cast', '-Werror=redundant-decls', '-Werror=return-type', '-Werror=sequence-point', '-Werror=shadow', '-Werror=strict-prototypes', '-Werror=trigraphs', '-Werror=undef', '-Werror=write-strings', '-Wformat-nonliteral', '-Wignored-qualifiers', '-Wimplicit-function-declaration', '-Wlogical-op', '-Wmissing-declarations', '-Wmissing-format-attribute', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wnested-externs', '-Wno-cast-function-type', '-Wno-dangling-pointer', '-Wno-missing-field-initializers', '-Wno-sign-compare', '-Wno-unused-parameter', '-Wold-style-definition', '-Wpointer-arith', '-Wredundant-decls', '-Wstrict-prototypes', '-Wswitch-default', '-Wswitch-enum', '-Wundef', '-Wuninitialized', '-Wunused', '-fno-strict-aliasing', ['-Werror=format-security', '-Werror=format=2'], ] if get_option('buildtype') != 'plain' test_c_args += '-fstack-protector-strong' endif foreach arg: test_c_args if cc.has_multi_arguments(arg) project_c_args += arg endif endforeach add_project_arguments(project_c_args, language: 'c') includes = [ include_directories('.'), include_directories('src') ] dependencies = [ dependency('glib-2.0', version : '>=2.16'), dependency('mm-glib', version : '>=1.14'), dependency('libsoup-3.0'), dependency('libcares', version : '>=1.18.1'), cc.find_library('dl'), cc.find_library('phonenumber', required: true) ] subdir('src') subdir('plugins') mmsd = executable('mmsdtng', 'src/main.c', dependencies : dependencies, include_directories : includes, install : true, link_with : [mms_lib, plugins_lib] ) subdir('tools') subdir('unit') mmsd-2.6.0/meson_options.txt000066400000000000000000000000701456374454100161260ustar00rootroot00000000000000option('build-mmsctl', type : 'boolean', value : false) mmsd-2.6.0/plugins/000077500000000000000000000000001456374454100141555ustar00rootroot00000000000000mmsd-2.6.0/plugins/meson.build000066400000000000000000000002511456374454100163150ustar00rootroot00000000000000plugins_sources = [ 'modemmanager.c', ] plugins_lib = static_library('plugins', plugins_sources, include_directories : includes, dependencies : dependencies, ) mmsd-2.6.0/plugins/modemmanager.c000066400000000000000000002100151456374454100167540ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2018 Purism SPC * 2020-2021 Chris Talbot * * * 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. * * 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 #endif #include #include #include #include #include "mms.h" #include "dbus.h" #include "phone-utils.h" #include "service-providers.h" // SETTINGS_STORE is synced with services.c #define SETTINGS_STORE "mms" // SETTINGS_GROUP is where we store our settings for this plugin #define SETTINGS_GROUP "Modem Manager" //Identifier of the plugin #define IDENTIFIER "modemmanager" //dbus default timeout for Modem #define MMSD_MM_MODEM_TIMEOUT 20000 #define MMS_MODEMMANAGER_INTERFACE MMS_SERVICE ".ModemManager" enum { MMSD_MM_STATE_NO_MANAGER, MMSD_MM_STATE_MANAGER_FOUND, MMSD_MM_STATE_NO_MODEM, MMSD_MM_STATE_MODEM_FOUND, MMSD_MM_STATE_NO_MESSAGING_MODEM, MMSD_MM_STATE_MODEM_DISABLED, MMSD_MM_STATE_READY } e_mmsd_connection; enum { MMSD_MM_MODEM_MMSC_MISCONFIGURED, //the MMSC is the default value MMSD_MM_MODEM_NO_BEARERS_ACTIVE, //The Modem has no bearers MMSD_MM_MODEM_INTERFACE_DISCONNECTED, //mmsd found the right bearer, but it is disconnected MMSD_MM_MODEM_INCORRECT_APN_CONNECTED, //no APN is connected that matches the settings MMSD_MM_MODEM_FUTURE_CASE_DISCONNECTED, //Reserved for future case MMSD_MM_MODEM_CONTEXT_ACTIVE //No error, context activated properly } mm_context_connection; struct modem_data { struct mms_service *service; //Do not mess with the guts of this in plugin.c! GKeyFile *modemsettings; //These are pulled from the settings file, and can be set via the Dbus char *message_center; // The mmsc char *mms_apn; // The MMS APN char *MMS_proxy; // I *think* this is where mms proxy goes? char *default_number; char *modem_number; // These are for settings the context (i.e. APN settings and if the bearer is active) char *context_interface; // Bearer interface here (e.g. "wwan0") char *context_path; // Dbus path of the bearer gboolean context_active; // Whether the context is active //The Bus org.ofono.mms.ModemManager guint owner_id; guint registration_id; gulong modem_state_watch_id; gulong sms_wap_signal_id; //These are modem manager related settings MMManager *mm; guint mm_watch_id; MMObject *object; MMModem *modem; char *path; MMSim *sim; gchar *imsi; gchar *mccmnc; char *registered_imsi; MMModemMessaging *modem_messaging; MMModemState state; GPtrArray *device_arr; gboolean modem_available; gboolean modem_ready; gboolean manager_available; gboolean plugin_registered; gboolean auto_process_on_connection; gboolean autoprocess_sms_wap; gboolean get_all_sms_timeout; gboolean modem_number_known; }; typedef struct { MMObject *object; MMModem *modem; MMSim *sim; } MmsdDevice; /* Introspection data for the service we are exporting */ static const gchar introspection_xml_modemmanager[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static GDBusNodeInfo *introspection_data = NULL; struct modem_data *modem; static void mmsd_mm_state (int state); static void mmsd_modem_available (void); static void mmsd_modem_unavailable (void); static void free_device (MmsdDevice *device); static void bearer_handler (mms_bool_t active, void *user_data); static int set_context (void); static void cb_mm_manager_new (GDBusConnection *connection, GAsyncResult *res, gpointer user_data); static void mm_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data); static void mm_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data); static int modemmanager_init (void); static void modemmanager_exit (void); static gboolean process_mms_process_message_queue (gpointer user_data); static void mmsd_connect_to_sms_wap (void); static void mmsd_disconnect_from_sms_wap (void); static void mmsd_get_all_sms (void); static void save_string_settings (GKeyFile *key_file, const gchar *group_name, const gchar *key, const gchar *value, gchar **location) { g_free (*location); *location = g_strdup (value); DBG ("%s set to '%s'", key, value); if (!value || !*value) value = "NULL"; g_key_file_set_string (key_file, group_name, key, value); } static void save_boolean_settings (GKeyFile *key_file, const gchar *group_name, const gchar *key, gboolean value, gboolean *location) { *location = value; DBG ("%s set to %s", key, value ? "TRUE" : "FALSE"); g_key_file_set_boolean (key_file, group_name, key, value); } static gboolean dbus_change_settings (const char *key, GVariant *value) { GKeyFile *key_file; const gchar *val_str; gboolean val_bool; key_file = modem->modemsettings; if (g_strcmp0 (key, "CarrierMMSC") == 0) { g_variant_get (value, "&s", &val_str); save_string_settings (key_file, SETTINGS_GROUP, key, val_str, &modem->message_center); } else if (g_strcmp0 (key, "MMS_APN") == 0) { g_variant_get (value, "&s", &val_str); save_string_settings (key_file, SETTINGS_GROUP, key, val_str, &modem->mms_apn); } else if (g_strcmp0 (key, "CarrierMMSProxy") == 0) { g_variant_get (value, "&s", &val_str); if (!*val_str || g_strcmp0 (val_str, "NULL") == 0) val_str = NULL; save_string_settings (key_file, SETTINGS_GROUP, key, val_str, &modem->MMS_proxy); } else if (g_strcmp0 (key, "DefaultModemNumber") == 0) { g_variant_get (value, "&s", &val_str); if (!*val_str || g_strcmp0 (val_str, "NULL") == 0) val_str = NULL; save_string_settings (key_file, SETTINGS_GROUP, key, val_str, &modem->default_number); } else if (g_strcmp0 (key, "ModemNumber") == 0) { const char *service_modem_number = NULL; service_modem_number = mms_service_get_own_number (modem->service); if (modem->modem_number_known == TRUE) { g_warning ("Modem Number is known. Not setting ModemNumber"); return TRUE; } g_variant_get (value, "&s", &val_str); if (!*val_str || g_strcmp0 (val_str, "NULL") == 0) val_str = NULL; if ((g_strcmp0 (val_str, service_modem_number) != 0) && (service_modem_number != NULL)) { g_warning ("New Modem Number '%s' is different than current modem number '%s'", val_str, service_modem_number); g_warning ("Now using number '%s'", val_str); } g_clear_pointer (&modem->modem_number, g_free); modem->modem_number = g_strdup (val_str); if (mms_service_set_own_number (modem->service, modem->modem_number)) save_string_settings (key_file, SETTINGS_GROUP, key, val_str, &modem->modem_number); } else if (g_strcmp0 (key, "AutoProcessOnConnection") == 0) { g_variant_get (value, "b", &val_bool); if (val_bool == TRUE && val_bool != modem->auto_process_on_connection) process_mms_process_message_queue (NULL); save_boolean_settings (key_file, SETTINGS_GROUP, key, val_bool, &modem->auto_process_on_connection); } else if (g_strcmp0 (key, "AutoProcessSMSWAP") == 0) { g_variant_get (value, "b", &val_bool); if (val_bool == FALSE && modem->autoprocess_sms_wap == TRUE && modem->modem_available == TRUE) mmsd_disconnect_from_sms_wap (); if (val_bool == TRUE && modem->autoprocess_sms_wap == FALSE) { mmsd_connect_to_sms_wap (); if (modem->modem_ready == TRUE) mmsd_get_all_sms (); } save_boolean_settings (key_file, SETTINGS_GROUP, key, val_bool, &modem->auto_process_on_connection); } else return FALSE; return TRUE; } static void handle_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { if (g_strcmp0 (method_name, "PushNotify") == 0) { GVariant *smswap; const unsigned char *data; gsize data_len; if (modem->modem_ready == TRUE) { g_variant_get (parameters, "(@ay)", &smswap); data_len = g_variant_get_size (smswap); data = g_variant_get_fixed_array (smswap, &data_len, 1); DBG ("%s", __func__); mms_service_push_notify (modem->service, data, data_len); g_dbus_method_invocation_return_value (invocation, NULL); } else g_dbus_method_invocation_return_dbus_error (invocation, MMS_MODEMMANAGER_INTERFACE, "Modem is not active!"); } else if (g_strcmp0 (method_name, "ViewSettings") == 0) { GVariantBuilder settings_builder; GVariant *settings, *all_settings; const char *service_modem_number = NULL; g_variant_builder_init (&settings_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add_parsed (&settings_builder, "{'CarrierMMSC', <%s>}", modem->message_center); g_variant_builder_add_parsed (&settings_builder, "{'MMS_APN', <%s>}", modem->mms_apn); if (modem->MMS_proxy && *modem->MMS_proxy) g_variant_builder_add_parsed (&settings_builder, "{'CarrierMMSProxy', <%s>}", modem->MMS_proxy); else g_variant_builder_add_parsed (&settings_builder, "{'CarrierMMSProxy', <%s>}", "NULL"); if (modem->default_number) g_variant_builder_add_parsed (&settings_builder, "{'DefaultModemNumber', <%s>}", modem->default_number); else g_variant_builder_add_parsed (&settings_builder, "{'DefaultModemNumber', <%s>}", "NULL"); service_modem_number = mms_service_get_own_number (modem->service); if (modem->modem_number) g_variant_builder_add_parsed (&settings_builder, "{'ModemNumber', <%s>}", modem->modem_number); else if (service_modem_number) g_variant_builder_add_parsed (&settings_builder, "{'ModemNumber', <%s>}", service_modem_number); else g_variant_builder_add_parsed (&settings_builder, "{'ModemNumber', <''>}"); g_variant_builder_add_parsed (&settings_builder, "{'AutoProcessOnConnection', <%b>}", modem->auto_process_on_connection); g_variant_builder_add_parsed (&settings_builder, "{'AutoProcessSMSWAP', <%b>}", modem->autoprocess_sms_wap); settings = g_variant_builder_end (&settings_builder); all_settings = g_variant_new ("(*)", settings); g_dbus_method_invocation_return_value (invocation, all_settings); } else if (g_strcmp0 (method_name, "ChangeSettings") == 0) { g_autoptr(GVariant) value = NULL; g_autofree char *key = NULL; gboolean close_settings = FALSE, has_item = FALSE; if (modem->modemsettings == NULL) { close_settings = TRUE; modem->modemsettings = mms_settings_open (IDENTIFIER, SETTINGS_STORE); } g_variant_get (parameters, "(sv)", &key, &value); if (dbus_change_settings (key, value)) has_item = TRUE; if (close_settings == TRUE) { mms_settings_close (IDENTIFIER, SETTINGS_STORE, modem->modemsettings, has_item); modem->modemsettings = NULL; } else if (has_item) mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings); if (!has_item) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Cannot find the Property requested!"); return; } g_dbus_method_invocation_return_value (invocation, NULL); if (!modem->MMS_proxy) modem->MMS_proxy = g_strdup ("NULL"); if (!*modem->MMS_proxy) { g_clear_pointer (&modem->MMS_proxy, g_free); modem->MMS_proxy = g_strdup ("NULL"); } g_dbus_connection_emit_signal (connection, NULL, MMS_PATH, MMS_MODEMMANAGER_INTERFACE, "SettingsChanged", g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy), NULL); if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0) g_clear_pointer (&modem->MMS_proxy, g_free); } else if (g_strcmp0 (method_name, "ChangeAllSettings") == 0) { g_autoptr(GVariant) properties = NULL; GVariantIter *iter; GVariant *value; char *key; gboolean close_settings = FALSE, has_item = FALSE; if (modem->modemsettings == NULL) { close_settings = TRUE; modem->modemsettings = mms_settings_open (IDENTIFIER, SETTINGS_STORE); } g_variant_get (parameters, "(@a{sv})", &properties); g_variant_get (properties, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{sv}", &key, &value)) if (dbus_change_settings (key, value)) has_item = TRUE; if (close_settings == TRUE) { mms_settings_close (IDENTIFIER, SETTINGS_STORE, modem->modemsettings, has_item); modem->modemsettings = NULL; } else if (has_item) mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings); if (!has_item) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Cannot find the Property requested!"); return; } g_dbus_method_invocation_return_value (invocation, NULL); if (!modem->MMS_proxy) modem->MMS_proxy = g_strdup ("NULL"); if (!*modem->MMS_proxy) { g_clear_pointer (&modem->MMS_proxy, g_free); modem->MMS_proxy = g_strdup ("NULL"); } g_dbus_connection_emit_signal (connection, NULL, MMS_PATH, MMS_MODEMMANAGER_INTERFACE, "SettingsChanged", g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy), NULL); if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0) g_clear_pointer (&modem->MMS_proxy, g_free); } else if (g_strcmp0 (method_name, "ProcessMessageQueue") == 0) { if (modem->modem_ready == TRUE) { process_mms_process_message_queue (NULL); g_dbus_method_invocation_return_value (invocation, NULL); } else g_dbus_method_invocation_return_dbus_error (invocation, MMS_MODEMMANAGER_INTERFACE, "Modem is not connected!"); } } static const GDBusInterfaceVTable interface_vtable = { handle_method_call }; static void cb_sms_delete_finish_retry (MMModemMessaging *modemmessaging, GAsyncResult *result, MMSms *sms) { g_autoptr(GError) error = NULL; if (mm_modem_messaging_delete_finish (modemmessaging, result, &error)) DBG ("Message delete finish"); else DBG ("Couldn't delete SMS - error: %s", error ? error->message : "unknown"); } static void cb_sms_delete_finish (MMModemMessaging *modemmessaging, GAsyncResult *result, MMSms *sms) { g_autoptr(GError) error = NULL; if (mm_modem_messaging_delete_finish (modemmessaging, result, &error)) DBG ("Message delete finish"); else { const char *path; path = mm_sms_get_path (sms); DBG ("Couldn't delete SMS - error: %s", error ? error->message : "unknown"); if (path) { DBG ("Trying to delete SMS again...."); mm_modem_messaging_delete (modem->modem_messaging, path, NULL, (GAsyncReadyCallback)cb_sms_delete_finish_retry, sms); } else DBG ("mmsd-tng cannot process MMS at this time!"); } } static gboolean mmsd_mm_activate_bearer (gpointer user_data) { activate_bearer (modem->service); return FALSE; } static gboolean process_mms_process_message_queue (gpointer user_data) { if (modem->modem_ready == TRUE) { DBG ("Processing any unsent/unreceived MMS messages."); /* * Prevent a race condition from the connection turning active to usable * for mmsd-tng */ g_timeout_add_seconds (1, mmsd_mm_activate_bearer, NULL); } else DBG ("Modem is not ready to process any unsent/unreceived MMS messages."); return FALSE; } static void mmsd_process_sms (MMSms *sms) { const gchar *message; const guint8 *data; const char *path; gsize data_len; data_len = 0; message = mm_sms_get_text (sms); data = mm_sms_get_data (sms, &data_len); if (message) DBG ("This is a regular SMS."); else if (data) { DBG ("Received SMS WAP!"); if (modem->modem_ready == TRUE) { mms_service_push_notify (modem->service, data, data_len); path = mm_sms_get_path (sms); if (path) { DBG ("Deleting SMS Now..."); mm_modem_messaging_delete (modem->modem_messaging, path, NULL, (GAsyncReadyCallback)cb_sms_delete_finish, sms); } else DBG ("mmsd-tng cannot process MMS at this time!"); } else DBG ("Modem is not connected!"); } else g_critical ("Not sure what kind of SMS this is!"); } static void cb_sms_state_change (MMSms *sms, GParamSpec *pspec, gpointer *user_data) { MMSmsState state; state = mm_sms_get_state (sms); DBG ("%s: state %s", __func__, mm_sms_state_get_string (mm_sms_get_state (sms))); if (state == MM_SMS_STATE_RECEIVED) mmsd_process_sms (sms); } static void mmsd_check_pdu_type (MMSms *sms) { MMSmsState state; MMSmsPduType pdu_type; pdu_type = mm_sms_get_pdu_type (sms); state = mm_sms_get_state (sms); switch (pdu_type) { case MM_SMS_PDU_TYPE_CDMA_DELIVER: case MM_SMS_PDU_TYPE_DELIVER: if (state == MM_SMS_STATE_RECEIVED) mmsd_process_sms (sms); if (state == MM_SMS_STATE_RECEIVING) // The first chunk of a multipart SMS has been // received -> wait for MM_SMS_STATE_RECEIVED g_signal_connect (sms, "notify::state", G_CALLBACK (cb_sms_state_change), NULL); break; case MM_SMS_PDU_TYPE_STATUS_REPORT: case MM_SMS_PDU_TYPE_SUBMIT: DBG ("This is not an SMS being received, do not care"); break; case MM_SMS_PDU_TYPE_UNKNOWN: DBG ("Unknown PDU type"); break; case MM_SMS_PDU_TYPE_CDMA_SUBMIT: case MM_SMS_PDU_TYPE_CDMA_CANCELLATION: case MM_SMS_PDU_TYPE_CDMA_DELIVERY_ACKNOWLEDGEMENT: case MM_SMS_PDU_TYPE_CDMA_USER_ACKNOWLEDGEMENT: case MM_SMS_PDU_TYPE_CDMA_READ_ACKNOWLEDGEMENT: default: DBG ("PDU type not handled"); } } static void cb_sms_list_new_ready (MMModemMessaging *modemmessaging, GAsyncResult *result, gchar *path) { GList *l, *list; g_autoptr(GError) error = NULL; MMSms *sms; list = mm_modem_messaging_list_finish (modemmessaging, result, &error); if (error) g_critical ("Couldn't get SMS list - error: %s", error->message); else { for (l = list; l; l = g_list_next (l)) //We are searching for the SMS from the list that is new if (!g_strcmp0 (mm_sms_get_path (MM_SMS (l->data)), path)) { sms = g_object_ref (MM_SMS (l->data)); mmsd_check_pdu_type (sms); break; } g_list_free_full (list, g_object_unref); g_free (path); } } static gboolean cb_dbus_signal_sms_added (MMModemMessaging *device, gchar *const_path, gpointer user_data) { gchar *path; path = g_strdup (const_path); DBG ("Got new SMS"); mm_modem_messaging_list (modem->modem_messaging, NULL, (GAsyncReadyCallback)cb_sms_list_new_ready, path); return TRUE; } static void cb_sms_list_all_ready (MMModemMessaging *modemmessaging, GAsyncResult *result, gpointer user_data) { GList *l, *list; g_autoptr(GError) error = NULL; MMSms *sms; list = mm_modem_messaging_list_finish (modemmessaging, result, &error); if (error) DBG ("Couldn't get SMS list - error: %s", error->message); else { for (l = list; l; l = g_list_next (l)) { sms = g_object_ref (MM_SMS (l->data)); mmsd_check_pdu_type (sms); } g_list_free_full (list, g_object_unref); } } static gboolean mmsd_mm_get_messaging_list (gpointer user_data) { DBG ("Searching for all SMS"); mm_modem_messaging_list (modem->modem_messaging, NULL, (GAsyncReadyCallback)cb_sms_list_all_ready, NULL); return FALSE; } static void mmsd_get_all_sms (void) { g_return_if_fail (MM_IS_MODEM_MESSAGING (modem->modem_messaging)); if (modem->get_all_sms_timeout == FALSE) { DBG ("Queue Search for all SMS"); modem->get_all_sms_timeout = TRUE; g_timeout_add_seconds (5, mmsd_mm_get_messaging_list, NULL); } } static void mmsd_disconnect_from_sms_wap (void) { MmGdbusModemMessaging *gdbus_sms; if (modem->modem_messaging == NULL) { g_critical ("SMS WAP Disconnect: There is no modem with messaging capabilities!"); return; } DBG ("Stopping watching SMS WAPs"); gdbus_sms = MM_GDBUS_MODEM_MESSAGING (modem->modem_messaging); g_signal_handler_disconnect (gdbus_sms, modem->sms_wap_signal_id); } static void mmsd_connect_to_sms_wap (void) { MmGdbusModemMessaging *gdbus_sms; if (modem->modem_messaging == NULL) { DBG ("SMS WAP Connect: There is no modem with messaging capabilities!"); return; } gdbus_sms = MM_GDBUS_MODEM_MESSAGING (modem->modem_messaging); DBG ("Watching for new SMS WAPs"); modem->sms_wap_signal_id = g_signal_connect (gdbus_sms, "added", G_CALLBACK (cb_dbus_signal_sms_added), NULL); } static gboolean mmsd_mm_init_modem (MMObject *obj) { modem->modem_messaging = mm_object_get_modem_messaging (MM_OBJECT (obj)); if (modem->modem_messaging == NULL) return FALSE; modem->object = g_object_ref (obj); modem->modem = mm_object_get_modem (MM_OBJECT (obj)); modem->path = mm_modem_dup_path (modem->modem); g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (modem->modem), MMSD_MM_MODEM_TIMEOUT); DBG ("%s", __func__); return TRUE; } static void free_device (MmsdDevice *device) { if (!device) return; g_clear_object (&device->sim); g_clear_object (&device->modem); g_clear_object (&device->object); g_free (device); } static gboolean device_match_by_object (MmsdDevice *device, GDBusObject *object) { g_return_val_if_fail (G_IS_DBUS_OBJECT (object), FALSE); g_return_val_if_fail (MM_OBJECT (device->object), FALSE); return object == G_DBUS_OBJECT (device->object); } static void mmsd_mm_add_object (MMObject *obj) { MmsdDevice *device; const gchar *object_path; const gchar *const *modem_number_ref; MMSim *sim; const gchar *country_code; g_autoptr(GError) error = NULL; g_autofree char *modem_number_formatted = NULL; object_path = g_dbus_object_get_object_path (G_DBUS_OBJECT (obj)); g_return_if_fail (object_path); //Begin if statement if (g_ptr_array_find_with_equal_func (modem->device_arr, obj, (GEqualFunc)device_match_by_object, NULL)) { //End if statement DBG ("Device %s already added", object_path); return; } if (modem->modem_available) { DBG ("There is already a modem registered!"); return; } if (modem->default_number && *modem->default_number) { g_autofree char *default_number_formatted = NULL; DBG ("Checking if this modem number matches default number"); sim = mm_modem_get_sim_sync (mm_object_get_modem (MM_OBJECT (obj)), NULL, &error); if (error != NULL) { g_critical ("Error Getting Sim: %s", error->message); return; } country_code = get_country_iso_for_mcc (mm_sim_get_imsi (sim)); modem_number_ref = mm_modem_get_own_numbers (mm_object_get_modem (MM_OBJECT (obj))); for (guint i = 0; modem_number_ref && modem_number_ref[i]; i++) { const char *number; number = modem_number_ref[i]; if (number) { modem_number_formatted = phone_utils_format_number_e164 (number, country_code, FALSE); break; } } if (modem_number_ref == NULL) { DBG ("Could not get number!"); return; } default_number_formatted = phone_utils_format_number_e164 (modem->default_number, country_code, FALSE); if (default_number_formatted == NULL) { g_critical ("Default number not formatted correctly!"); return; } if (g_strcmp0 (modem_number_formatted, default_number_formatted) != 0) { DBG ("This modem does not match default number!"); return; } } else DBG ("Not checking for a default Modem"); DBG ("Added device at: %s", object_path); if (mmsd_mm_init_modem (obj) == TRUE) { device = g_new0 (MmsdDevice, 1); device->object = g_object_ref (MM_OBJECT (obj)); device->modem = mm_object_get_modem (MM_OBJECT (obj)); g_ptr_array_add (modem->device_arr, device); mmsd_mm_state (MMSD_MM_STATE_MODEM_FOUND); } else mmsd_mm_state (MMSD_MM_STATE_NO_MESSAGING_MODEM); } static void mmsd_mm_get_modems (void) { GList *list, *l; gboolean has_modem = FALSE; DBG ("getting Modems"); g_return_if_fail (MM_IS_MANAGER (modem->mm)); list = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (modem->mm)); for (l = list; l != NULL; l = l->next) { if (!mm_object_peek_modem_messaging (l->data)) continue; has_modem = TRUE; mmsd_mm_add_object (MM_OBJECT (l->data)); } if (!has_modem) mmsd_mm_state (MMSD_MM_STATE_NO_MODEM); if (list) g_list_free_full (list, g_object_unref); } static void cb_object_added (GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) { DBG ("%s", __func__); if (mm_object_peek_modem_messaging (MM_OBJECT (object))) { g_message ("New Object with Messaging feature found, Adding..."); mmsd_mm_add_object (MM_OBJECT (object)); } } static void cb_object_removed (GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) { guint index; g_return_if_fail (G_IS_DBUS_OBJECT (object)); g_return_if_fail (G_IS_DBUS_OBJECT_MANAGER (manager)); //Begin if statement if (g_ptr_array_find_with_equal_func (modem->device_arr, object, (GEqualFunc)device_match_by_object, &index)) //End if Statement g_ptr_array_remove_index_fast (modem->device_arr, index); if (MM_OBJECT (object) == modem->object) mmsd_mm_state (MMSD_MM_STATE_NO_MODEM); DBG ("Modem removed: %s", g_dbus_object_get_object_path (object)); } static void cb_name_owner_changed (GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) { gchar *name_owner; name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (manager)); if (!name_owner) mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER); DBG ("Name owner changed"); g_free (name_owner); } static void cb_mm_manager_new (GDBusConnection *connection, GAsyncResult *res, gpointer user_data) { gchar *name_owner; g_autoptr(GError) error = NULL; modem->mm = mm_manager_new_finish (res, &error); modem->device_arr = g_ptr_array_new_with_free_func ((GDestroyNotify) free_device); if (modem->mm) { mmsd_mm_state (MMSD_MM_STATE_MANAGER_FOUND); g_signal_connect (modem->mm, "interface-added", G_CALLBACK (cb_object_added), NULL); g_signal_connect (modem->mm, "object-added", G_CALLBACK (cb_object_added), NULL); g_signal_connect (modem->mm, "object-removed", G_CALLBACK (cb_object_removed), NULL); g_signal_connect (modem->mm, "notify::name-owner", G_CALLBACK (cb_name_owner_changed), NULL); name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (modem->mm)); DBG ("ModemManager found: %s\n", name_owner); g_free (name_owner); mmsd_mm_get_modems (); } else { g_critical ("Error connecting to ModemManager: %s\n", error->message); mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER); } } static void mmsd_mm_get_modem_state (void) { if (!modem->modem) { mmsd_mm_state (MMSD_MM_STATE_NO_MODEM); return; } if (modem->state < MM_MODEM_STATE_ENABLED) { DBG ("Something May be wrong with the modem, checking...."); switch (modem->state) { case MM_MODEM_STATE_FAILED: DBG ("MM_MODEM_STATE_FAILED"); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; case MM_MODEM_STATE_UNKNOWN: DBG ("MM_MODEM_STATE_UNKNOWN"); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; case MM_MODEM_STATE_LOCKED: DBG ("MM_MODEM_STATE_FAILED"); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; case MM_MODEM_STATE_INITIALIZING: DBG ("MM_MODEM_STATE_INITIALIZING"); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; case MM_MODEM_STATE_DISABLED: DBG ("MM_MODEM_STATE_DISABLED"); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; case MM_MODEM_STATE_DISABLING: DBG ("MM_MODEM_STATE_DISABLING"); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; case MM_MODEM_STATE_ENABLING: DBG ("MM_MODEM_STATE_ENABLING"); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; case MM_MODEM_STATE_ENABLED: case MM_MODEM_STATE_SEARCHING: case MM_MODEM_STATE_REGISTERED: case MM_MODEM_STATE_DISCONNECTING: case MM_MODEM_STATE_CONNECTING: case MM_MODEM_STATE_CONNECTED: g_warning ("This code should not be reached"); break; default: DBG ("MM_MODEM_OTHER_BAD_STATE: %d", modem->state); mmsd_mm_state (MMSD_MM_STATE_MODEM_DISABLED); return; } } DBG ("MM_MODEM_GOOD_STATE: %d", modem->state); mmsd_mm_state (MMSD_MM_STATE_READY); /* Automatically process unsent/unreceived messages when the modem is connected */ if (modem->state == MM_MODEM_STATE_CONNECTED) if (modem->auto_process_on_connection == TRUE) process_mms_process_message_queue (NULL); return; } static void modem_state_changed_cb (MMModem *cb_modem, MMModemState old, MMModemState new, MMModemStateChangeReason reason) { DBG ("State Change: Old State: %d New State: %d, Reason: %d", old, new, reason); modem->state = new; mmsd_mm_get_modem_state (); } static void mmsd_mm_state (int state) { switch (state) { case MMSD_MM_STATE_MODEM_FOUND: if (!modem->modem_available) mmsd_modem_available (); break; case MMSD_MM_STATE_NO_MODEM: if (modem->modem_available) { mmsd_modem_unavailable (); DBG ("Modem vanished, Disabling plugin"); } else DBG ("Could not connect to modem"); modem->modem_available = FALSE; modem->modem_ready = FALSE; DBG ("MMSD_MM_STATE_NO_MODEM"); break; case MMSD_MM_STATE_NO_MESSAGING_MODEM: DBG ("Modem has no messaging capabilities"); DBG ("MMSD_MM_STATE_NO_MESSAGING_MODEM"); modem->modem_available = FALSE; modem->modem_ready = FALSE; break; case MMSD_MM_STATE_MODEM_DISABLED: DBG ("Modem disabled"); DBG ("MMSD_MM_STATE_MODEM_DISABLED"); DBG ("Disabling Bearer Handler"); mms_service_set_bearer_handler (modem->service, NULL, NULL); modem->modem_ready = FALSE; break; case MMSD_MM_STATE_MANAGER_FOUND: if (!modem->manager_available) modem->manager_available = TRUE; DBG ("MMSD_MM_STATE_MANAGER_FOUND"); break; case MMSD_MM_STATE_NO_MANAGER: if (modem->modem_available) { mmsd_modem_unavailable (); DBG ("Modem vanished, Disabling plugin"); } if (modem->manager_available) { g_clear_object (&modem->mm); if (modem->device_arr) { g_ptr_array_set_size (modem->device_arr, 0); g_ptr_array_unref (modem->device_arr); modem->device_arr = NULL; } modem->modem_available = FALSE; modem->modem_ready = FALSE; } else DBG ("Could not connect to ModemManager"); modem->manager_available = FALSE; DBG ("MMSD_MM_STATE_NO_MANAGER"); break; case MMSD_MM_STATE_READY: DBG ("MMSD_MM_STATE_READY"); modem->modem_ready = TRUE; DBG ("Setting Bearer Handler"); mms_service_set_bearer_handler (modem->service, bearer_handler, modem); if (modem->autoprocess_sms_wap) mmsd_get_all_sms (); break; default: g_return_if_reached (); } } static void mm_appeared_cb (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { g_assert (G_IS_DBUS_CONNECTION (connection)); mm_manager_new (connection, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, NULL, (GAsyncReadyCallback) cb_mm_manager_new, NULL); } static void mm_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_assert (G_IS_DBUS_CONNECTION (connection)); DBG ("Modem Manager vanished"); mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER); } static void bearer_handler (mms_bool_t active, void *user_data) { gint32 response; GDBusConnection *connection = mms_dbus_get_connection (); /* Check for any errors within the context */ g_strstrip (modem->mms_apn); g_strstrip (modem->message_center); if (modem->MMS_proxy != NULL) g_strstrip (modem->MMS_proxy); response = set_context (); if (response != MMSD_MM_MODEM_CONTEXT_ACTIVE) { DBG ("Set MMSC: '%s', Set Proxy: '%s', Set MMS APN: '%s'", modem->message_center, modem->MMS_proxy, modem->mms_apn); g_dbus_connection_emit_signal (connection, NULL, MMS_PATH, MMS_MODEMMANAGER_INTERFACE, "BearerHandlerError", g_variant_new ("(h)", response), NULL); } DBG ("At Bearer Handler: path '%s' active '%d' context_active '%d'", modem->path, active, modem->context_active); if (active == TRUE && modem->context_active == TRUE) { DBG ("active and context_active, bearer_notify"); mms_service_bearer_notify (modem->service, TRUE, modem->context_interface, modem->MMS_proxy); return; } else if (active == TRUE && modem->context_active == FALSE) { DBG ("Error activating context!"); mms_service_bearer_notify (modem->service, FALSE, NULL, NULL); return; } DBG ("checking for failure"); if (active == FALSE && modem->context_active == FALSE) { DBG ("Context not active!"); mms_service_bearer_notify (modem->service, FALSE, NULL, NULL); return; } else { DBG ("No failures"); mms_service_bearer_notify (modem->service, FALSE, modem->context_interface, modem->MMS_proxy); return; } } static void find_settings_cb (const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data) { if (error) { g_warning ("Could not find settings: %s", error->message); g_warning ("Your MMS settings are not in the database! Please file a merge request at https://gitlab.gnome.org/GNOME/mobile-broadband-provider-info so they can be added"); } else { DBG ("Found settings: APN:%s, mmsc: %s, Proxy: %s, MMS max size: %s", apn, mmsc, proxy, mmssize); if (mmsc != NULL) { GDBusConnection *connection = mms_dbus_get_connection (); g_free (modem->message_center); modem->message_center = g_strdup (mmsc); DBG ("Carrier MMSC set to %s", modem->message_center); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSC", modem->message_center); g_free (modem->mms_apn); modem->mms_apn = g_strdup (apn); DBG ("MMS APN set to %s", modem->mms_apn); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "MMS_APN", modem->mms_apn); g_free (modem->MMS_proxy); modem->MMS_proxy = NULL; if (proxy && *proxy) modem->MMS_proxy = g_strdup (proxy); else modem->MMS_proxy = g_strdup ("NULL"); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSProxy", modem->MMS_proxy); if (mmssize && *mmssize) service_set_max_attach_size (modem->service, atoi(mmssize)); else service_set_max_attach_size (modem->service, DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE); /* proxy will not be NULL here */ g_dbus_connection_emit_signal (connection, NULL, MMS_PATH, MMS_MODEMMANAGER_INTERFACE, "SettingsChanged", g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy), NULL); if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0) { g_free (modem->MMS_proxy); modem->MMS_proxy = NULL; } DBG ("MMS Proxy set to %s", modem->MMS_proxy); mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings); /* Reprocess message queue since settings are right now */ g_timeout_add_seconds (1, process_mms_process_message_queue, NULL); } else g_warning ("Your MMS settings are not in the database! Please file a merge request at https://gitlab.gnome.org/GNOME/mobile-broadband-provider-info so they can be added"); } } static int set_context (void) { guint max_bearers, active_bearers; GList *bearer_list, *l; MMBearer *modem_bearer; MMBearerProperties *modem_bearer_properties; const gchar *apn; gboolean interface_disconnected; gboolean bearer_connected; DBG ("Setting Context..."); if (modem->context_active) { g_clear_pointer (&modem->context_interface, g_free); g_clear_pointer (&modem->context_path, g_free); } modem->context_active = FALSE; interface_disconnected = FALSE; mms_service_set_mmsc (modem->service, modem->message_center); max_bearers = mm_modem_get_max_active_bearers (modem->modem); DBG ("Max number of bearers: %d", max_bearers); bearer_list = mm_modem_list_bearers_sync (modem->modem, NULL, NULL); active_bearers = 0; if (bearer_list != NULL) { MMBearerIpConfig *modem_bearer_ip6_config; const gchar **dns_ipv6; gchar *dns_ipv6_csv = NULL; MMBearerIpConfig *modem_bearer_ip4_config; const gchar **dns_ipv4; gchar *dns_ipv4_csv = NULL; for (l = bearer_list; l != NULL; l = l->next) { active_bearers = active_bearers + 1; modem_bearer = (MMBearer *) l->data; modem_bearer_properties = mm_bearer_get_properties (modem_bearer); apn = mm_bearer_properties_get_apn (modem_bearer_properties); if (g_strcmp0 (modem->message_center, "http://mms.invalid") == 0) { DBG ("Attempting to autoconfigure settings"); mmsd_service_providers_find_settings (MOBILE_BROADBAND_PROVIDER_INFO_DATABASE, modem->mccmnc, apn, find_settings_cb, NULL); } DBG ("Current Context APN: '%s', mmsd-tng settings MMS APN: '%s'", apn, modem->mms_apn); bearer_connected = mm_bearer_get_connected (modem_bearer); /* APNs are not case sensitive: https://stackoverflow.com/questions/20834806/is-apn-name-case-sensitive */ if ((apn != NULL) && (modem->mms_apn != NULL) && (g_ascii_strcasecmp (apn, modem->mms_apn) == 0)) { if (modem->state != MM_MODEM_STATE_CONNECTED) { DBG ("The modem interface is reporting it is disconnected!"); DBG ("Reported State: %d", modem->state); interface_disconnected = TRUE; } else if (!bearer_connected) interface_disconnected = TRUE; else { DBG ("You are connected to the correct APN! Enabling context..."); modem->context_interface = mm_bearer_dup_interface (modem_bearer); modem->context_path = mm_bearer_dup_path (modem_bearer); modem->context_active = TRUE; mms_service_set_apn (modem->service, modem->mms_apn); modem_bearer_ip4_config = mm_bearer_peek_ipv4_config (modem_bearer); dns_ipv4 = mm_bearer_ip_config_get_dns (modem_bearer_ip4_config); if (dns_ipv4) dns_ipv4_csv = g_strjoinv (",", (gchar **)dns_ipv4); modem_bearer_ip6_config = mm_bearer_peek_ipv6_config (modem_bearer); dns_ipv6 = mm_bearer_ip_config_get_dns (modem_bearer_ip6_config); if (dns_ipv6) dns_ipv6_csv = g_strjoinv (",", (gchar **)dns_ipv6); mms_service_set_resolvers (modem->service, dns_ipv4_csv, dns_ipv6_csv); DBG ("DNS from modemmanager IPv6: '%s', IPv4: '%s'", dns_ipv6_csv, dns_ipv4_csv); g_clear_pointer (&dns_ipv6_csv, g_free); g_clear_pointer (&dns_ipv4_csv, g_free); } } } g_list_free (bearer_list); g_list_free (l); if (!modem->context_active) // I did not find the right context I wanted. { if (active_bearers == max_bearers) { if (interface_disconnected) { DBG ("The proper context is not connected!"); return MMSD_MM_MODEM_INTERFACE_DISCONNECTED; } else { DBG ("The modem is not connected to the correct APN!"); return MMSD_MM_MODEM_INCORRECT_APN_CONNECTED; } } else if (active_bearers == 0) { DBG ("The modem bearer is disconnected! Please enable modem data"); return MMSD_MM_MODEM_NO_BEARERS_ACTIVE; } else if (active_bearers < max_bearers) { /* * TODO: Modem manager does not support this yet, but this is * where to add code when Modem manager supports multiple * contexts and/or a seperate MMS context. * The Pinephone and Librem 5 only support * one active bearer as well */ DBG ("This is a stub for adding a new context/bearer, but Modem Manager does not support this yet."); return MMSD_MM_MODEM_FUTURE_CASE_DISCONNECTED; } } } else { DBG ("There are no modem bearers! Please enable modem data"); return MMSD_MM_MODEM_NO_BEARERS_ACTIVE; } if (g_strcmp0 (modem->message_center, "http://mms.invalid") == 0) { DBG ("The MMSC is not configured! Please configure the MMSC and restart mmsd-tng."); return MMSD_MM_MODEM_MMSC_MISCONFIGURED; } return MMSD_MM_MODEM_CONTEXT_ACTIVE; } static void mm_save_settings (void) { g_autofree char *imsi_hash = NULL; /* Settings are default, no use in saving them */ if (g_strcmp0 (modem->message_center, "http://mms.invalid") == 0) return; imsi_hash = g_strdup_printf ("Old Settings: %u", g_str_hash (modem->registered_imsi)); g_key_file_set_string (modem->modemsettings, imsi_hash, "CarrierMMSC", modem->message_center); g_key_file_set_string (modem->modemsettings, imsi_hash, "MMS_APN", modem->mms_apn); if (modem->MMS_proxy) g_key_file_set_string (modem->modemsettings, imsi_hash, "CarrierMMSProxy", modem->MMS_proxy); else g_key_file_set_string (modem->modemsettings, imsi_hash, "CarrierMMSProxy", "NULL"); if (modem->modem_number) g_key_file_set_string (modem->modemsettings, imsi_hash, "ModemNumber", modem->modem_number); g_key_file_set_integer (modem->modemsettings, imsi_hash, "TotalMaxAttachmentSize", service_get_max_attach_size (modem->service)); mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings); } static gboolean mm_retrieve_settings (void) { g_autofree char *imsi_hash = NULL; g_autoptr(GError) error = NULL; int max_mms_size = 0; imsi_hash = g_strdup_printf ("Old Settings: %u", g_str_hash (modem->imsi)); modem->message_center = g_key_file_get_string (modem->modemsettings, imsi_hash, "CarrierMMSC", &error); if (error) return FALSE; modem->mms_apn = g_key_file_get_string (modem->modemsettings, imsi_hash, "MMS_APN", &error); if (error) { g_clear_pointer (&modem->message_center, g_free); return FALSE; } modem->MMS_proxy = g_key_file_get_string (modem->modemsettings, imsi_hash, "CarrierMMSProxy", &error); if (error) { g_clear_pointer (&modem->message_center, g_free); g_clear_pointer (&modem->mms_apn, g_free); return FALSE; } modem->modem_number = g_key_file_get_string (modem->modemsettings, imsi_hash, "ModemNumber", &error); if (error) { modem->modem_number = NULL; g_clear_error (&error); } max_mms_size = g_key_file_get_integer (modem->modemsettings, imsi_hash, "TotalMaxAttachmentSize", &error); if (error) { max_mms_size = DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE; g_clear_error (&error); } service_set_max_attach_size (modem->service, max_mms_size); return TRUE; } static void mmsd_modem_available (void) { g_autoptr(GError) error = NULL; modem->modem_available = TRUE; if (modem->modem) { const char *imsi = NULL; const char *const *modem_number_ref; MmGdbusModem *gdbus_modem; if (modem->plugin_registered == FALSE) { DBG ("Registering Modem Manager MMS Service"); mms_service_register (modem->service); modem->modemsettings = mms_service_get_keyfile (modem->service); modem->plugin_registered = TRUE; } modem->sim = mm_modem_get_sim_sync (modem->modem, NULL, &error); if (error == NULL) imsi = mm_sim_get_imsi (modem->sim); else g_warning ("Error Getting Sim: %s", error->message); if (imsi != NULL) { modem->imsi = g_strdup (imsi); g_free (modem->mccmnc); modem->mccmnc = mm_sim_dup_operator_identifier (modem->sim); if (!modem->mccmnc) modem->mccmnc = g_strndup (modem->imsi, 6); if (g_strcmp0 (modem->imsi, modem->registered_imsi) != 0) { if (g_strcmp0 (modem->registered_imsi, "invalid") != 0) { GDBusConnection *connection = mms_dbus_get_connection (); DBG ("IMSI Changed"); mm_save_settings (); g_clear_pointer (&modem->message_center, g_free); g_clear_pointer (&modem->mms_apn, g_free); g_clear_pointer (&modem->MMS_proxy, g_free); g_clear_pointer (&modem->modem_number, g_free); if (mm_retrieve_settings ()) DBG ("Reloading settings"); else { DBG ("Resetting settings"); modem->message_center = g_strdup ("http://mms.invalid"); modem->mms_apn = g_strdup ("apn.invalid"); modem->MMS_proxy = g_strdup ("NULL"); service_set_max_attach_size (modem->service, DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE); } g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSC", modem->message_center); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "MMS_APN", modem->mms_apn); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSProxy", modem->MMS_proxy); if (modem->modem_number) g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "ModemNumber", modem->modem_number); else g_key_file_remove_key (modem->modemsettings, SETTINGS_GROUP, "ModemNumber", NULL); g_dbus_connection_emit_signal (connection, NULL, MMS_PATH, MMS_MODEMMANAGER_INTERFACE, "SettingsChanged", g_variant_new ("(sss)", modem->mms_apn, modem->message_center, modem->MMS_proxy), NULL); if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0) g_clear_pointer (&modem->MMS_proxy, g_free); } g_free (modem->registered_imsi); modem->registered_imsi = g_strdup (modem->imsi); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "IMSI", modem->registered_imsi); mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings); } mms_service_set_country_code (modem->service, mm_sim_get_imsi (modem->sim)); } else g_warning ("IMSI is NULL!"); modem->modem_number_known = FALSE; modem_number_ref = mm_modem_get_own_numbers (modem->modem); for (guint i = 0; modem_number_ref && modem_number_ref[i]; i++) { const char *number; number = modem_number_ref[i]; if (number) { if (modem->modem_number) { g_warning ("mmsd-tng was provided a number '%s' but modem manager found a number '%s'", modem->modem_number, number); g_warning ("mmsd-tng is deleting the provided number '%s'", modem->modem_number); g_key_file_remove_key (modem->modemsettings, SETTINGS_GROUP, "ModemNumber", NULL); mms_settings_sync (IDENTIFIER, SETTINGS_STORE, modem->modemsettings); } modem->modem_number_known = TRUE; mms_service_set_own_number (modem->service, number); break; } else if (modem->modem_number) { DBG ("Could not find modem number, using number from settings"); mms_service_set_own_number (modem->service, modem->modem_number); break; } } gdbus_modem = MM_GDBUS_MODEM (modem->modem); modem->modem_state_watch_id = g_signal_connect (gdbus_modem, "state-changed", G_CALLBACK (modem_state_changed_cb), NULL); if (modem->autoprocess_sms_wap) mmsd_connect_to_sms_wap (); else DBG ("Not autoprocessing SMS WAPs"); modem->state = mm_modem_get_state (modem->modem); mmsd_mm_get_modem_state (); } else g_critical ("Something very bad happened at mmsd_modem_available()!"); } static void mmsd_modem_unavailable (void) { MmGdbusModem *gdbus_modem; DBG ("Disabling Bearer Handler"); gdbus_modem = MM_GDBUS_MODEM (modem->modem); mms_service_set_bearer_handler (modem->service, NULL, NULL); if (modem->autoprocess_sms_wap) mmsd_disconnect_from_sms_wap (); modem->get_all_sms_timeout = FALSE; g_signal_handler_disconnect (gdbus_modem, modem->modem_state_watch_id); g_object_unref (modem->sim); g_clear_pointer (&modem->mccmnc, g_free); g_clear_pointer (&modem->modem_number, g_free); g_free (modem->imsi); g_free (modem->path); g_clear_object (&modem->modem); g_clear_object (&modem->modem_messaging); g_clear_object (&modem->object); modem->modem_available = FALSE; modem->modem_ready = FALSE; } static int modemmanager_init (void) { GDBusConnection *connection = mms_dbus_get_connection (); g_autoptr(GError) error = NULL; DBG ("Starting Modem Manager Plugin!"); // Set up modem Structure to be used here modem = g_try_new0 (struct modem_data, 1); if (modem == NULL) { g_critical ("Could not allocate space for modem data!"); return -ENOMEM; } modem->service = mms_service_create (); mms_service_set_identity (modem->service, IDENTIFIER); modem->modemsettings = mms_settings_open (IDENTIFIER, SETTINGS_STORE); modem->message_center = g_key_file_get_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSC", &error); if (error) { DBG ("No MMSC was configured!"); modem->message_center = g_strdup ("http://mms.invalid"); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSC", modem->message_center); g_clear_error (&error); } modem->mms_apn = g_key_file_get_string (modem->modemsettings, SETTINGS_GROUP, "MMS_APN", &error); if (error) { DBG ("No MMS APN was configured!"); modem->mms_apn = g_strdup ("apn.invalid"); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "MMS_APN", modem->mms_apn); g_clear_error (&error); } modem->MMS_proxy = g_key_file_get_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSProxy", &error); if (error) { DBG ("Setting MMS Procy to NULL"); modem->MMS_proxy = g_strdup ("NULL"); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "CarrierMMSProxy", modem->MMS_proxy); g_clear_error (&error); } if (g_strcmp0 (modem->MMS_proxy, "NULL") == 0 || !*modem->MMS_proxy) g_clear_pointer (&modem->MMS_proxy, g_free); modem->default_number = g_key_file_get_string (modem->modemsettings, SETTINGS_GROUP, "DefaultModemNumber", &error); if (error) { DBG ("No Default Modem Number was configured! Setting to NULL."); modem->default_number = g_strdup ("NULL"); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "DefaultModemNumber", modem->default_number); g_clear_error (&error); } if (g_strcmp0 (modem->default_number, "NULL") == 0 || !*modem->default_number) g_clear_pointer (&modem->default_number, g_free); modem->auto_process_on_connection = g_key_file_get_boolean (modem->modemsettings, SETTINGS_GROUP, "AutoProcessOnConnection", &error); if (error) { DBG ("Auto Process On Connection was not configured! Setting to TRUE."); modem->auto_process_on_connection = TRUE; g_key_file_set_boolean (modem->modemsettings, SETTINGS_GROUP, "AutoProcessOnConnection", modem->auto_process_on_connection); g_clear_error (&error); } modem->autoprocess_sms_wap = g_key_file_get_boolean (modem->modemsettings, SETTINGS_GROUP, "AutoProcessSMSWAP", &error); if (error) { DBG ("Auto Process SMS WAP was not configured! Setting to TRUE."); modem->autoprocess_sms_wap = TRUE; g_key_file_set_boolean (modem->modemsettings, SETTINGS_GROUP, "AutoProcessSMSWAP", modem->autoprocess_sms_wap); g_clear_error (&error); } modem->registered_imsi = g_key_file_get_string (modem->modemsettings, SETTINGS_GROUP, "IMSI", &error); if (error) { modem->registered_imsi = g_strdup ("invalid"); g_key_file_set_string (modem->modemsettings, SETTINGS_GROUP, "IMSI", modem->registered_imsi); g_clear_error (&error); } modem->modem_number = g_key_file_get_string (modem->modemsettings, SETTINGS_GROUP, "ModemNumber", &error); if (error) { DBG ("No Modem Number was configured."); modem->modem_number = NULL; g_clear_error (&error); } mms_settings_close (IDENTIFIER, SETTINGS_STORE, modem->modemsettings, TRUE); modem->modemsettings = NULL; introspection_data = g_dbus_node_info_new_for_xml (introspection_xml_modemmanager, NULL); g_assert (introspection_data != NULL); modem->modem_available = FALSE; modem->modem_ready = FALSE; modem->manager_available = FALSE; modem->context_active = FALSE; modem->plugin_registered = FALSE; modem->get_all_sms_timeout = FALSE; modem->modem_number_known = FALSE; modem->registration_id = g_dbus_connection_register_object (connection, MMS_PATH, introspection_data->interfaces[0], &interface_vtable, NULL, /* user_data */ NULL, /* user_data_free_func */ &error); /* GError** */ if (error) { g_critical ("Error registering MMSD ModemManager interface: %s\n", error->message); g_clear_error (&error); } modem->mm_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, MM_DBUS_SERVICE, G_BUS_NAME_WATCHER_FLAGS_AUTO_START, (GBusNameAppearedCallback) mm_appeared_cb, (GBusNameVanishedCallback) mm_vanished_cb, NULL, NULL); return 0; } static void modemmanager_exit (void) { GDBusConnection *connection = mms_dbus_get_connection (); if (modem->modem_available) mmsd_mm_state (MMSD_MM_STATE_NO_MODEM); if (modem->manager_available) mmsd_mm_state (MMSD_MM_STATE_NO_MANAGER); if (modem->context_active) { g_clear_pointer (&modem->context_interface, g_free); g_clear_pointer (&modem->context_path, g_free); } if (modem->plugin_registered == TRUE) { mms_service_unregister (modem->service); modem->modemsettings = NULL; } mms_service_unref (modem->service); g_dbus_connection_unregister_object (connection, modem->registration_id); g_free (modem->registered_imsi); g_free (modem->message_center); g_free (modem->MMS_proxy); g_free (modem->mms_apn); g_bus_unwatch_name (modem->mm_watch_id); g_free (modem); g_dbus_node_info_unref (introspection_data); } MMS_PLUGIN_DEFINE (modemmanager, modemmanager_init, modemmanager_exit) mmsd-2.6.0/src/000077500000000000000000000000001456374454100132635ustar00rootroot00000000000000mmsd-2.6.0/src/dbus.c000066400000000000000000000027451456374454100143740ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2021, Chris Talbot * * 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. * * 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 #endif #include #include "dbus.h" #include "mms.h" static GDBusConnection *connection; static GDBusNodeInfo *introspection_data = NULL; GDBusConnection * mms_dbus_get_connection (void) { return connection; } void __mms_dbus_set_connection (GDBusConnection *conn) { connection = conn; } GDBusNodeInfo * mms_dbus_get_introspection_data (void) { return introspection_data; } void __mms_dbus_set_introspection_data (void) { introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); g_assert (introspection_data != NULL); } void __mms_dbus_unref_introspection_data (void) { g_dbus_node_info_unref (introspection_data); } mmsd-2.6.0/src/dbus.h000066400000000000000000000073241456374454100143770ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2021, Chris Talbot * * 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. * * 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 #define MMS_SERVICE "org.ofono.mms" #define MMS_PATH "/org/ofono/mms" #define MMS_MANAGER_INTERFACE MMS_SERVICE ".Manager" #define MMS_SERVICE_INTERFACE MMS_SERVICE ".Service" #define MMS_MESSAGE_INTERFACE MMS_SERVICE ".Message" #define MMS_ERROR_INTERFACE MMS_SERVICE ".Error" GDBusConnection *mms_dbus_get_connection (void); GDBusNodeInfo *mms_dbus_get_introspection_data (void); void __mms_dbus_set_connection (GDBusConnection *conn); void __mms_dbus_set_introspection_data (void); void __mms_dbus_unref_introspection_data (void); /* ---------------------------------------------------------------------------------------------------- */ /* * GIO Dbus Server modified from here: * https://gitlab.gnome.org/GNOME/glib/-/blob/master/gio/tests/gdbus-example-server.c */ /* Introspection data for the service we are exporting */ static const gchar introspection_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; /* ---------------------------------------------------------------------------------------------------- */ mmsd-2.6.0/src/genbuiltin000077500000000000000000000003421456374454100153500ustar00rootroot00000000000000#!/bin/sh for i in $* do echo "extern struct mms_plugin_desc __mms_builtin_$i;" done echo echo "static struct mms_plugin_desc *__mms_builtin[] = {" for i in $* do echo " &__mms_builtin_$i," done echo " NULL" echo "};" mmsd-2.6.0/src/itu-e212-iso.c000066400000000000000000000120011456374454100154610ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2020, Purism SPC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 or later 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. * * 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 #endif #include #include "itu-e212-iso.h" struct mcc_list { guint mcc; char code[3]; }; /* * Extracted from: * https://www.itu.int/dms_pub/itu-t/opb/sp/T-SP-E.212B-2018-PDF-E.pdf */ struct mcc_list mcc_list[] = { {202, "GR"}, {204, "NL"}, {206, "BE"}, {208, "FR"}, {212, "MC"}, {213, "AD"}, {214, "ES"}, {216, "HU"}, {218, "BA"}, {219, "HR"}, {220, "RS"}, {221, "XK"}, {222, "IT"}, {225, "VA"}, {226, "RO"}, {228, "CH"}, {230, "CZ"}, {231, "SK"}, {232, "AT"}, {234, "GB"}, {235, "GB"}, {238, "DK"}, {240, "SE"}, {242, "NO"}, {244, "FI"}, {246, "LT"}, {247, "LV"}, {248, "EE"}, {250, "RU"}, {255, "UA"}, {257, "BY"}, {259, "MD"}, {260, "PL"}, {262, "DE"}, {266, "GI"}, {268, "PT"}, {270, "LU"}, {272, "IE"}, {274, "IS"}, {276, "AL"}, {278, "MT"}, {280, "CY"}, {282, "GE"}, {283, "AM"}, {284, "BG"}, {286, "TR"}, {288, "FO"}, {290, "GL"}, {292, "SM"}, {293, "SI"}, {294, "MK"}, {295, "LI"}, {297, "ME"}, {302, "CA"}, {308, "PM"}, {310, "US"}, {311, "US"}, {312, "US"}, {313, "US"}, {314, "US"}, {315, "US"}, {316, "US"}, {330, "PR"}, {332, "VI"}, {334, "MX"}, {338, "JM"}, /* Guadeloupe and Martinique are part of France */ {340, "GP"}, {340, "MQ"}, {342, "BB"}, {344, "AG"}, {346, "KY"}, {348, "VG"}, {350, "BM"}, {352, "GD"}, {354, "MS"}, {356, "KN"}, {358, "LC"}, {360, "VC"}, {362, "CW"}, {363, "AW"}, {364, "BS"}, {365, "AI"}, {366, "DM"}, {368, "CU"}, {370, "DO"}, {372, "HT"}, {374, "TT"}, {376, "TC"}, {400, "AZ"}, {401, "KZ"}, {402, "BT"}, {404, "IN"}, {405, "IN"}, {406, "IN"}, {410, "PK"}, {412, "AF"}, {413, "LK"}, {414, "MM"}, {415, "LB"}, {416, "JO"}, {417, "SY"}, {418, "IQ"}, {419, "KW"}, {420, "SA"}, {421, "YE"}, {422, "OM"}, {424, "AE"}, {425, "IL"}, {426, "BH"}, {427, "QA"}, {428, "MN"}, {429, "NP"}, {430, "AE"}, {431, "AE"}, {432, "IR"}, {434, "UZ"}, {436, "TJ"}, {437, "KG"}, {438, "TM"}, {440, "JP"}, {441, "JP"}, {450, "KP"}, {452, "VN"}, {454, "HK"}, {455, "MO"}, {456, "KH"}, {457, "LA"}, {460, "CN"}, {461, "CN"}, {466, "TW"}, {467, "KR"}, {470, "BD"}, {472, "MV"}, {502, "MY"}, {505, "AU"}, {510, "ID"}, {514, "TL"}, {515, "PH"}, {520, "TH"}, {525, "SG"}, {528, "BN"}, {530, "NZ"}, {536, "NR"}, {537, "PG"}, {539, "TO"}, {540, "SB"}, {541, "VU"}, {542, "FJ"}, {543, "WF"}, {544, "AS"}, {545, "KI"}, {546, "NC"}, {547, "PF"}, {548, "CK"}, {549, "AS"}, {550, "FM"}, {551, "MH"}, {552, "PW"}, {553, "TV"}, {554, "TK"}, {555, "NU"}, {602, "EG"}, {603, "DZ"}, {604, "MA"}, {605, "TN"}, {606, "LY"}, {607, "GM"}, {608, "SN"}, {609, "MR"}, {610, "ML"}, {611, "GN"}, {612, "CI"}, {613, "BF"}, {614, "NE"}, {615, "TG"}, {616, "BJ"}, {617, "MU"}, {618, "LR"}, {619, "SL"}, {620, "GH"}, {621, "NG"}, {622, "TD"}, {623, "CF"}, {624, "CM"}, {625, "CV"}, {626, "ST"}, {627, "GQ"}, {628, "GA"}, {629, "CG"}, {630, "CD"}, {631, "AO"}, {632, "GW"}, {633, "SC"}, {634, "SD"}, {635, "RW"}, {636, "ET"}, {637, "SO"}, {638, "DJ"}, {639, "KE"}, {640, "TZ"}, {641, "UG"}, {642, "BI"}, {643, "MZ"}, {645, "ZM"}, {646, "MG"}, {647, "RE"}, {648, "ZW"}, {649, "NA"}, {650, "MW"}, {651, "LS"}, {652, "BW"}, {653, "SZ"}, {654, "KM"}, {655, "ZA"}, {657, "ER"}, {658, "SH"}, {659, "SS"}, {702, "BZ"}, {704, "GT"}, {706, "SV"}, {708, "HN"}, {710, "NI"}, {712, "CR"}, {714, "PA"}, {716, "PE"}, {722, "AR"}, {724, "BR"}, {730, "CL"}, {732, "CO"}, {734, "VE"}, {736, "BO"}, {738, "GY"}, {740, "EC"}, {742, "GF"}, {744, "PY"}, {746, "SR"}, {748, "UY"}, {750, "FK"}, }; const char * get_country_iso_for_mcc (const char *mcc_str) { g_autofree char *str = NULL; guint64 mcc; if (!mcc_str || strlen (mcc_str) < 3) return NULL; str = g_strndup (mcc_str, 3); mcc = g_ascii_strtoull (str, NULL, 10); for (guint i = 0; i < G_N_ELEMENTS (mcc_list); i++) if (mcc_list[i].mcc == mcc) return mcc_list[i].code; g_warning ("invalid MCC code: %" G_GUINT64_FORMAT, mcc); return NULL; } mmsd-2.6.0/src/itu-e212-iso.h000066400000000000000000000015401456374454100154740ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2020, Purism SPC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 or later 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. * * 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 const char * get_country_iso_for_mcc (const char *mcc_str); mmsd-2.6.0/src/log.h000066400000000000000000000020361456374454100142160ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 * */ #define DBG(fmt, arg ...) do { \ g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "%s:%s() " fmt, \ __FILE__, __FUNCTION__, ## arg); \ } while (0) mmsd-2.6.0/src/main.c000066400000000000000000000105031456374454100143520ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include #include #include #include #include "version.h" #include "mms.h" #include "dbus.h" static GMainLoop *main_loop = NULL; static volatile sig_atomic_t __terminated = 0; static void sig_term (int sig) { if (__terminated > 0) return; __terminated = 1; g_message ("Terminating"); g_main_loop_quit (main_loop); } static gboolean option_version = FALSE; static gboolean global_debug = FALSE; static GOptionEntry options[] = { { "debug", 'd', 0, G_OPTION_ARG_NONE, &global_debug, "Enable debugging output" }, { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, "Show version information and exit" }, { NULL }, }; static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { DBG ("Dbus Bus acquired!"); __mms_dbus_set_connection (connection); __mms_service_init (global_debug); __mms_plugin_init (); } static void on_name_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { DBG ("Dbus name acquired!"); } static void on_name_lost (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_critical ("Lost Dbus Connection! Exiting...."); exit (1); } int main (int argc, char *argv[]) { GOptionContext *context; GError *error = NULL; struct sigaction sa; guint owner_id; g_autofree char *mms_home_path = NULL; context = g_option_context_new (NULL); g_option_context_add_main_entries (context, options, NULL); if (g_option_context_parse (context, &argc, &argv, &error) == FALSE) { if (error != NULL) { g_printerr ("%s\n", error->message); g_error_free (error); } else g_printerr ("An unknown error occurred\n"); exit (1); } g_option_context_free (context); if (option_version == TRUE) { printf ("%s, git version: %s\n", VERSION, PACKAGE_VCS_VERSION); exit (0); } /* Check if the home/.mms directory can be written to */ mms_home_path = g_build_filename (g_get_home_dir () , ".mms", NULL); if (g_mkdir_with_parents (mms_home_path, 0700) != 0) g_printerr ("Cannot create directory in '%s'\n", mms_home_path); if (access (mms_home_path, R_OK | W_OK) != 0) { g_printerr ("Cannot read and write in '%s', exiting...\n", mms_home_path); exit (1); } if (global_debug == TRUE) g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); main_loop = g_main_loop_new (NULL, FALSE); __mms_dbus_set_introspection_data (); owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, MMS_SERVICE, G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL); g_message ("MMSD-TNG version %s, git version: %s", VERSION, PACKAGE_VCS_VERSION); memset (&sa, 0, sizeof(sa)); sa.sa_handler = sig_term; sigaction (SIGINT, &sa, NULL); sigaction (SIGTERM, &sa, NULL); g_main_loop_run (main_loop); __mms_plugin_cleanup (); __mms_service_cleanup (); g_message ("EXIT"); g_bus_unown_name (owner_id); __mms_dbus_unref_introspection_data (); g_main_loop_unref (main_loop); return 0; } mmsd-2.6.0/src/meson.build000066400000000000000000000013031456374454100154220ustar00rootroot00000000000000mms_sources = [ 'dbus.c', 'service.c', 'store.c', 'mmsutil.c', 'service-providers.c', 'wsputil.c', 'plugin.c', 'itu-e212-iso.c', 'phone-utils.cpp' ] builtin_plugins = [ 'modemmanager' ] genbuiltin = find_program('genbuiltin') if genbuiltin.found() builtin = custom_target('generate builtin.h', output : 'builtin.h', capture : true, command : [genbuiltin] + builtin_plugins ) mms_sources = mms_sources + [builtin] endif revision_tag = vcs_tag( input: 'version.h.in', output: 'version.h', ) dependencies += declare_dependency(sources: revision_tag) mms_lib = static_library('mms', mms_sources, include_directories : includes, dependencies : dependencies, ) mmsd-2.6.0/src/mms.h000066400000000000000000000021431456374454100142300ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #include "itu-e212-iso.h" #include "log.h" #include "plugin.h" int __mms_plugin_init (void); void __mms_plugin_cleanup (void); #include "service.h" int __mms_service_init (gboolean enable_debug); void __mms_service_cleanup (void); #include "store.h" mmsd-2.6.0/src/mmsutil.c000066400000000000000000001776621456374454100151440ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * 2020, Anteater * * 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. * * 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 #endif #include #include #include #include #include #include "wsputil.h" #include "mmsutil.h" #include "mms.h" #define MAX_ENC_VALUE_BYTES 6 #ifdef TEMP_FAILURE_RETRY #define TFR TEMP_FAILURE_RETRY #else #define TFR #endif #define uninitialized_var(x) x = x enum header_flag { HEADER_FLAG_MANDATORY = 1, HEADER_FLAG_ALLOW_MULTI = 2, HEADER_FLAG_PRESET_POS = 4, HEADER_FLAG_MARKED = 8, }; /* * See http://www.openmobilealliance.org/release/MMS/V1_3-20110913-A/OMA-TS-MMS_ENC-V1_3-20110913-A.pdf * Table 25 for a list of these headers. */ // Internal usage by mmsd-tng Hex and Name on Table 25 enum mms_header { MMS_HEADER_BCC = 0x01, // Bcc MMS_HEADER_CC = 0x02, // Cc MMS_HEADER_CONTENT_LOCATION = 0x03, // X-Mms-Content-Location MMS_HEADER_CONTENT_TYPE = 0x04, // Content-Type MMS_HEADER_DATE = 0x05, // Date MMS_HEADER_DELIVERY_REPORT = 0x06, // X-Mms-Delivery-Report MMS_HEADER_DELIVERY_TIME = 0x07, // X-Mms-Delivery-Time MMS_HEADER_EXPIRY = 0x08, // X-Mms-Expiry MMS_HEADER_FROM = 0x09, // From MMS_HEADER_MESSAGE_CLASS = 0x0a, // X-Mms-Message-Class MMS_HEADER_MESSAGE_ID = 0x0b, // Message-ID MMS_HEADER_MESSAGE_TYPE = 0x0c, // X-Mms-Message-Type MMS_HEADER_MMS_VERSION = 0x0d, // X-Mms-MMS-Version MMS_HEADER_MESSAGE_SIZE = 0x0e, // X-Mms-Message-Size MMS_HEADER_PRIORITY = 0x0f, // X-Mms-Priority MMS_HEADER_READ_REPLY = 0x10, // X-Mms-Read-Report MMS_HEADER_REPORT_ALLOWED = 0x11, // X-Mms-Report-Allowed MMS_HEADER_RESPONSE_STATUS = 0x12, // X-Mms-Response-Status MMS_HEADER_RESPONSE_TEXT = 0x13, // X-Mms-Response-Text MMS_HEADER_SENDER_VISIBILITY = 0x14, // X-Mms-Sender-Visibility MMS_HEADER_STATUS = 0x15, // X-Mms-Status MMS_HEADER_SUBJECT = 0x16, // Subject MMS_HEADER_TO = 0x17, // To MMS_HEADER_TRANSACTION_ID = 0x18, // X-Mms-Transaction-Id MMS_HEADER_RETRIEVE_STATUS = 0x19, // X-Mms-Retrieve-Status MMS_HEADER_RETRIEVE_TEXT = 0x1a, // X-Mms-Retrieve-Text MMS_HEADER_READ_STATUS = 0x1b, // X-Mms-Read-Status MMS_HEADER_REPLY_CHARGING = 0x1c, // X-Mms-Reply-Charging MMS_HEADER_REPLY_CHARGING_DEADLINE = 0x1d, // X-Mms-Reply-Charging-Deadline MMS_HEADER_REPLY_CHARGING_ID = 0x1e, // X-Mms-Reply-Charging-ID MMS_HEADER_REPLY_CHARGING_SIZE = 0x1f, // X-Mms-Reply-Charging-Size MMS_HEADER_PREVIOUSLY_SENT_BY = 0x20, // X-Mms-Previously-Sent-By MMS_HEADER_PREVIOUSLY_SENT_DATE = 0x21, // X-Mms-Previously-Sent-Date MMS_HEADER_STORE = 0x22, // X-Mms-Store MMS_HEADER_MM_STATE = 0x23, // X-Mms-MM-State MMS_HEADER_MM_FLAGS = 0x24, // X-Mms-MM-Flags MMS_HEADER_STORE_STATUS = 0x25, // X-Mms-Store-Status MMS_HEADER_STORE_STATUS_TEXT = 0x26, // X-Mms-Store-Status-Text MMS_HEADER_STORED = 0x27, // X-Mms-Stored MMS_HEADER_ATTRIBUTES = 0x28, // X-Mms-Attributes MMS_HEADER_TOTALS = 0x29, // X-Mms-Totals MMS_HEADER_MBOX_TOTALS = 0x2a, // X-Mms-Mbox-Totals MMS_HEADER_QUOTAS = 0x2b, // X-Mms-Quotas MMS_HEADER_MBOX_QUOTAS = 0x2c, // X-Mms-Mbox-Quotas MMS_HEADER_MESSAGE_COUNT = 0x2d, // X-Mms-Message-Count MMS_HEADER_CONTENT = 0x2e, // Content MMS_HEADER_START = 0x2f, // X-Mms-Start MMS_HEADER_ADDITIONAL_HEADERS = 0x30, // Additional-headers MMS_HEADER_DISTRIBUTION_INDICATOR = 0x31, // X-Mms-Distribution-Indicator MMS_HEADER_ELEMENT_DESCRIPTOR = 0x32, // X-Mms-Element-Descriptor MMS_HEADER_LIMIT = 0x33, // X-Mms-Limit MMS_HEADER_RECOMMENDED_RETRIEVAL_MODE = 0x34, // X-Mms-Recommended-Retrieval-Mode MMS_HEADER_RECOMMENDED_RETRIEVAL_MODE_TEXT = 0x35, // X-Mms-Recommended-Retrieval-Mode-Text MMS_HEADER_STATUS_TEST = 0x36, // X-Mms-Status-Text MMS_HEADER_APPLICATION_ID = 0x37, // X-Mms-Applic-ID MMS_HEADER_REPLY_APPLICATION_ID = 0x38, // X-Mms-Reply-Applic-ID MMS_HEADER_AUX_APPLICATION_INFO = 0x39, // X-Mms-Aux-Applic-Info MMS_HEADER_CONTENT_CLASS = 0x3a, // X-Mms-Content-Class MMS_HEADER_DRM_CONTACT = 0x3b, // X-Mms-DRM-Content MMS_HEADER_ADAPTATION_ALLOWED = 0x3c, // X-Mms-Adaptation-Allowed MMS_HEADER_REPLACE_ID = 0x3d, // X-Mms-Replace-ID MMS_HEADER_CANCEL_ID = 0x3e, // X-Mms-Cancel-ID MMS_HEADER_CANCEL_STATUS = 0x3f, // X-Mms-Cancel-Status __MMS_HEADER_MAX = 0x40, // Used to indicate largest header in mmsd-tng MMS_HEADER_INVALID = 0x80, // Used to indicate the end of the headers in mmsd-tng }; enum mms_part_header { MMS_PART_HEADER_CONTENT_LOCATION = 0x0e, MMS_PART_HEADER_CONTENT_ID = 0x40, }; /* * IANA Character Set Assignments (examples) used by WAPWSP * * Reference: WAP-230-WSP Appendix Table 42 Character Set Assignment Examples * Reference: IANA http://www.iana.org/assignments/character-sets */ static const struct { unsigned int mib_enum; const char *charset; } charset_assignments[] = { { 0x03, "us-ascii" }, { 0x6A, "utf-8" }, { 0x00, NULL } }; #define FB_SIZE 256 struct file_buffer { unsigned char buf[FB_SIZE]; unsigned int size; unsigned int fsize; int fd; }; typedef gboolean (*header_handler)(struct wsp_header_iter *, void *); typedef gboolean (*header_encoder)(struct file_buffer *, enum mms_header, void *); char * mms_content_type_get_param_value (const char *content_type, const char *param_name) { struct wsp_text_header_iter iter; if (wsp_text_header_iter_init (&iter, content_type) == FALSE) return NULL; while (wsp_text_header_iter_param_next (&iter) == TRUE) { const char *key = wsp_text_header_iter_get_key (&iter); if (g_str_equal (key, param_name) == TRUE) return g_strdup (wsp_text_header_iter_get_value (&iter)); } return NULL; } static const char * charset_index2string (unsigned int index) { unsigned int i = 0; for (i = 0; charset_assignments[i].charset; i++) if (charset_assignments[i].mib_enum == index) return charset_assignments[i].charset; return NULL; } static gboolean extract_short (struct wsp_header_iter *iter, void *user) { unsigned char *out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); *out = p[0]; return TRUE; } static const char * decode_text (struct wsp_header_iter *iter) { const unsigned char *p; unsigned int l = 32; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_TEXT) { p = wsp_header_iter_get_val (iter); DBG ("could not decode text of (dummy) length %u: %*s", l - 1, l - 1, p + 1); return NULL; } p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); DBG ("claimed len: %u", l); DBG ("val: %*s", l - 1, p); return wsp_decode_text (p, l, NULL); } static gboolean extract_text (struct wsp_header_iter *iter, void *user) { char **out = user; const char *text; text = decode_text (iter); if (text == NULL) return FALSE; *out = g_strdup (text); return TRUE; } static char * remove_address_type_suffix (const char *addr, size_t len) { return g_strdup (addr); /* #define MMS_ADDR_SUFFIX_PUBLIC_LAND_MOBILE_NUMBER "/TYPE=PLMN" * if(g_str_has_suffix(addr, MMS_ADDR_SUFFIX_PUBLIC_LAND_MOBILE_NUMBER)) { * return g_strndup(addr, len - strlen(MMS_ADDR_SUFFIX_PUBLIC_LAND_MOBILE_NUMBER)); * } else { * return g_strdup(addr); * }*/ } static char * decode_encoded_string_with_mib_enum (const unsigned char *p, unsigned int l) { unsigned int mib_enum; unsigned int consumed; const char *text; const char *from_codeset; const char *to_codeset = "UTF-8"; gsize bytes_read; gsize bytes_written; if (wsp_decode_integer (p, l, &mib_enum, &consumed) == FALSE) return NULL; if (mib_enum == 106) { /* header is UTF-8 already */ text = wsp_decode_text (p + consumed, l - consumed, NULL); return g_strdup (text); } /* convert to UTF-8 */ from_codeset = charset_index2string (mib_enum); if (from_codeset == NULL) return NULL; return g_convert ((const char *) p + consumed, l - consumed, to_codeset, from_codeset, &bytes_read, &bytes_written, NULL); } static gboolean extract_text_array_element (struct wsp_header_iter *iter, void *user) { char **out = user; const char *wsp_decoded_text; char *tmp; const unsigned char *p; unsigned int l; g_autofree char *element = NULL; p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); switch (wsp_header_iter_get_val_type (iter)) { case WSP_VALUE_TYPE_TEXT: /* Text-string */ wsp_decoded_text = wsp_decode_text (p, l, NULL); element = g_strdup (wsp_decoded_text); break; case WSP_VALUE_TYPE_LONG: /* (Value-len) Char-set Text-string */ element = decode_encoded_string_with_mib_enum (p, l); break; case WSP_VALUE_TYPE_SHORT: element = NULL; break; default: g_warning ("Unhandled case"); element = NULL; break; } if (element == NULL) { DBG ("failed, type=%d", wsp_header_iter_get_val_type (iter)); return FALSE; } if (*out == NULL) { *out = g_strdup (element); return TRUE; } tmp = g_strjoin (",", *out, element, NULL); if (tmp == NULL) { DBG ("join failed"); return FALSE; } g_free (*out); *out = tmp; return TRUE; } static gboolean extract_encoded_text (struct wsp_header_iter *iter, void *user) { char **out = user; const unsigned char *p; unsigned int l; const char *text; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wuninitialized" char *uninitialized_var (dec_text); #pragma GCC diagnostic pop p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); if (l == 0) { DBG ("Length is 0! Returning empty string"); dec_text = g_strdup (""); *out = dec_text; return TRUE; } switch (wsp_header_iter_get_val_type (iter)) { case WSP_VALUE_TYPE_TEXT: /* Text-string */ text = wsp_decode_text (p, l, NULL); dec_text = g_strdup (text); break; case WSP_VALUE_TYPE_LONG: /* (Value-len) Char-set Text-string */ dec_text = decode_encoded_string_with_mib_enum (p, l); break; case WSP_VALUE_TYPE_SHORT: dec_text = NULL; break; default: g_warning ("Unhandled case"); dec_text = NULL; break; } if (dec_text == NULL) return FALSE; *out = dec_text; return TRUE; } static gboolean extract_date (struct wsp_header_iter *iter, void *user) { time_t *out = user; const unsigned char *p; unsigned int l; unsigned int i; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_LONG) return FALSE; p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); if (l > 4) return FALSE; for (i = 0, *out = 0; i < l; i++) *out = *out << 8 | p[i]; /* It is possible to overflow time_t on 32 bit systems */ *out = *out & 0x7fffffff; return TRUE; } static gboolean extract_absolute_relative_date (struct wsp_header_iter *iter, void *user) { time_t *out = user; const unsigned char *p; unsigned int l; unsigned int i; unsigned int seconds; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_LONG) return FALSE; p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); if (l < 2 || l > 5) return FALSE; if (p[0] != 128 && p[0] != 129) return FALSE; for (i = 2, seconds = 0; i < l; i++) seconds = seconds << 8 | p[i]; if (p[0] == 129) { *out = time (NULL); *out += seconds; } else *out = seconds; /* It is possible to overflow time_t on 32 bit systems */ *out = *out & 0x7fffffff; return TRUE; } static gboolean extract_boolean (struct wsp_header_iter *iter, void *user) { gboolean *out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); if (p[0] != 128 && p[0] != 129) return FALSE; *out = p[0] == 128; return TRUE; } static gboolean extract_from (struct wsp_header_iter *iter, void *user) { char **out = user; const unsigned char *p; unsigned int l; const char *text; unsigned int val_len; unsigned int str_len; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_LONG) { DBG ("val_type not LONG"); return FALSE; } p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); /* From-value = Value-length (Address-present-token=128 Encoded-string-value | Insert-address-token=129) */ /* Encoded-string-value = Text-string | Value-length Char-set Text-string */ /* Value-length = Short-length | (Length-quote Length) */ /* Short-length = val 0-30 */ /* Length-quote = val 31 */ /* Length = Uintvar-integer */ if (p[0] != 128 && p[0] != 129) { DBG ("not 128 or 129"); return FALSE; } if (p[0] == 129) { *out = NULL; return TRUE; } p += 1; l -= 1; /* token has been handled */ val_len = l; if (p[0] < 31) /*short-length */ { val_len = p[0]; p += 2; val_len -= 1; /* count encoding against val_len */ } else if (p[0] == 31) /* length quote then long length */ { unsigned int consumed = 0; gboolean ok = wsp_decode_uintvar (p, l, &val_len, &consumed); if (!ok) return FALSE; p += consumed; val_len -= 1; /* count encoding against val_len */ } str_len = val_len - 1; /* NUL at the end is not counted by strlen() */ //DBG("trying to decode text of length %u: %*s", str_len, str_len, p); text = wsp_decode_text (p, val_len, NULL); //DBG("text=\"%s\"", text); if (text == NULL) { DBG ("could not decode text of length %u: %*s", str_len, str_len, p); return FALSE; } DBG ("Successfully decoded text!"); *out = remove_address_type_suffix (text, str_len); return TRUE; } static gboolean extract_message_class (struct wsp_header_iter *iter, void *user) { char **out = user; const unsigned char *p; unsigned int l; const char *text; if (wsp_header_iter_get_val_type (iter) == WSP_VALUE_TYPE_LONG) return FALSE; p = wsp_header_iter_get_val (iter); if (wsp_header_iter_get_val_type (iter) == WSP_VALUE_TYPE_SHORT) switch (p[0]) { case 128: *out = g_strdup ("Personal"); return TRUE; case 129: *out = g_strdup ("Advertisement"); return TRUE; case 130: *out = g_strdup ("Informational"); return TRUE; case 131: *out = g_strdup ("Auto"); return TRUE; default: return FALSE; } l = wsp_header_iter_get_val_len (iter); text = wsp_decode_token_text (p, l, NULL); if (text == NULL) return FALSE; *out = g_strdup (text); return TRUE; } static gboolean extract_sender_visibility (struct wsp_header_iter *iter, void *user) { enum mms_message_sender_visibility *out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); if (p[0] != 128 && p[0] != 129) return FALSE; *out = p[0]; return TRUE; } static gboolean extract_priority (struct wsp_header_iter *iter, void *user) { char **out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); switch (p[0]) { case 128: *out = g_strdup ("Low"); return TRUE; case 129: *out = g_strdup ("Normal"); return TRUE; case 130: *out = g_strdup ("High"); return TRUE; default: return FALSE; } return TRUE; } static gboolean extract_read_status (struct wsp_header_iter *iter, void *user) { enum mms_message_read_status *out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); if (p[0] == MMS_MESSAGE_READ_STATUS_READ || p[0] == MMS_MESSAGE_READ_STATUS_DELETED_UNREAD) { *out = p[0]; return TRUE; } return FALSE; } static gboolean extract_retr_status (struct wsp_header_iter *iter, void *user) { enum mms_message_retr_status *out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); switch (p[0]) { case MMS_MESSAGE_RETR_STATUS_OK: case MMS_MESSAGE_RETR_STATUS_ERR_TRANS_FAILURE: case MMS_MESSAGE_RETR_STATUS_ERR_TRANS_MESSAGE_NOT_FOUND: case MMS_MESSAGE_RETR_STATUS_ERR_PERM_FAILURE: case MMS_MESSAGE_RETR_STATUS_ERR_PERM_SERVICE_DENIED: case MMS_MESSAGE_RETR_STATUS_ERR_PERM_MESSAGE_NOT_FOUND: case MMS_MESSAGE_RETR_STATUS_ERR_PERM_CONTENT_UNSUPPORTED: *out = p[0]; return TRUE; default: g_warning ("Unhandled case"); return FALSE; } return FALSE; } static gboolean extract_rsp_status (struct wsp_header_iter *iter, void *user) { unsigned char *out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); switch (p[0]) { case MMS_MESSAGE_RSP_STATUS_OK: case MMS_MESSAGE_RSP_STATUS_ERR_UNSUPPORTED_MESSAGE: case MMS_MESSAGE_RSP_STATUS_ERR_TRANS_FAILURE: case MMS_MESSAGE_RSP_STATUS_ERR_TRANS_NETWORK_PROBLEM: case MMS_MESSAGE_RSP_STATUS_ERR_PERM_FAILURE: case MMS_MESSAGE_RSP_STATUS_ERR_PERM_SERVICE_DENIED: case MMS_MESSAGE_RSP_STATUS_ERR_PERM_MESSAGE_FORMAT_CORRUPT: case MMS_MESSAGE_RSP_STATUS_ERR_PERM_SENDING_ADDRESS_UNRESOLVED: case MMS_MESSAGE_RSP_STATUS_ERR_PERM_CONTENT_NOT_ACCEPTED: case MMS_MESSAGE_RSP_STATUS_ERR_PERM_LACK_OF_PREPAID: *out = p[0]; return TRUE; default: g_warning ("Unhandled case"); return FALSE; } return FALSE; } static gboolean extract_status (struct wsp_header_iter *iter, void *user) { enum mms_message_delivery_status *out = user; const unsigned char *p; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_SHORT) return FALSE; p = wsp_header_iter_get_val (iter); switch (p[0]) { case MMS_MESSAGE_DELIVERY_STATUS_EXPIRED: case MMS_MESSAGE_DELIVERY_STATUS_RETRIEVED: case MMS_MESSAGE_DELIVERY_STATUS_REJECTED: case MMS_MESSAGE_DELIVERY_STATUS_DEFERRED: case MMS_MESSAGE_DELIVERY_STATUS_UNRECOGNISED: case MMS_MESSAGE_DELIVERY_STATUS_INDETERMINATE: case MMS_MESSAGE_DELIVERY_STATUS_FORWARDED: case MMS_MESSAGE_DELIVERY_STATUS_UNREACHABLE: *out = p[0]; return TRUE; default: g_warning ("Unhandled case"); return FALSE; } return FALSE; } static gboolean extract_unsigned (struct wsp_header_iter *iter, void *user) { unsigned long *out = user; const unsigned char *p; unsigned int l; unsigned int i; if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_LONG) return FALSE; p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); if (l > sizeof(unsigned long)) return FALSE; for (i = 0, *out = 0; i < l; i++) *out = *out << 8 | p[i]; return TRUE; } static header_handler handler_for_type (enum mms_header header) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" //Refer to enum mms_header if you need to add headers switch (header) { case MMS_HEADER_BCC: return extract_encoded_text; case MMS_HEADER_CC: return extract_encoded_text; case MMS_HEADER_CONTENT_LOCATION: return extract_text; case MMS_HEADER_CONTENT_TYPE: return extract_text; /* extract_encoded_text? */ case MMS_HEADER_DATE: return extract_date; case MMS_HEADER_DELIVERY_REPORT: return extract_boolean; case MMS_HEADER_DELIVERY_TIME: return extract_absolute_relative_date; case MMS_HEADER_EXPIRY: return extract_absolute_relative_date; case MMS_HEADER_FROM: return extract_from; case MMS_HEADER_MESSAGE_CLASS: return extract_message_class; case MMS_HEADER_MESSAGE_ID: return extract_text; case MMS_HEADER_MESSAGE_TYPE: return extract_short; case MMS_HEADER_MMS_VERSION: return extract_short; case MMS_HEADER_MESSAGE_SIZE: return extract_unsigned; case MMS_HEADER_PRIORITY: return extract_priority; case MMS_HEADER_READ_REPLY: return extract_boolean; case MMS_HEADER_REPORT_ALLOWED: return extract_boolean; case MMS_HEADER_RESPONSE_STATUS: return extract_rsp_status; case MMS_HEADER_RESPONSE_TEXT: return extract_encoded_text; case MMS_HEADER_RETRIEVE_STATUS: return extract_retr_status; case MMS_HEADER_RETRIEVE_TEXT: return extract_encoded_text; case MMS_HEADER_READ_STATUS: return extract_read_status; case MMS_HEADER_SENDER_VISIBILITY: return extract_sender_visibility; case MMS_HEADER_STATUS: return extract_status; case MMS_HEADER_SUBJECT: return extract_encoded_text; case MMS_HEADER_TO: return extract_text_array_element; case MMS_HEADER_TRANSACTION_ID: return extract_text; case MMS_HEADER_INVALID: case __MMS_HEADER_MAX: default: return NULL; } #pragma GCC diagnostic pop } struct header_handler_entry { int flags; void *data; int pos; }; static gboolean mms_parse_headers (struct wsp_header_iter *iter, enum mms_header orig_header, ...) { struct header_handler_entry entries[__MMS_HEADER_MAX + 1]; va_list args; const unsigned char *p; unsigned int i; enum mms_header header; memset (&entries, 0, sizeof(entries)); va_start (args, orig_header); header = orig_header; while (header != MMS_HEADER_INVALID) { entries[header].flags = va_arg (args, int); entries[header].data = va_arg (args, void *); header = va_arg (args, enum mms_header); } va_end (args); for (i = 1; wsp_header_iter_next (iter); i++) { unsigned char h; header_handler handler; /* Skip application headers */ if (wsp_header_iter_get_hdr_type (iter) != WSP_HEADER_TYPE_WELL_KNOWN) continue; p = wsp_header_iter_get_hdr (iter); h = p[0] & 0x7f; handler = handler_for_type (h); if (handler == NULL) { if (h == MMS_HEADER_INVALID) { g_critical ("Got MMS_HEADER_INVALID: 0x%02X. Returning False", h); return FALSE; } else if (h == __MMS_HEADER_MAX) { g_critical ("Got __MMS_HEADER_MAX: 0x%02X. Returning False", h); return FALSE; } else { DBG ("Header 0x%02X is not handled in decoding. Skipping....", h); continue; } } DBG ("saw header of type 0x%02X", h); /* Unsupported header, skip */ if (entries[h].data == NULL) continue; /* Skip multiply present headers unless explicitly requested */ if ((entries[h].flags & HEADER_FLAG_MARKED) && !(entries[h].flags & HEADER_FLAG_ALLOW_MULTI)) continue; DBG ("running handler for type 0x%02X", h); /* Parse the header */ if (handler (iter, entries[h].data) == FALSE) { DBG ("handler %p for type 0x%02X returned false", handler, h); return FALSE; } DBG ("handler for type 0x%02X was success", h); entries[h].pos = i; entries[h].flags |= HEADER_FLAG_MARKED; } for (i = 0; i < __MMS_HEADER_MAX + 1; i++) if ((entries[i].flags & HEADER_FLAG_MANDATORY) && !(entries[i].flags & HEADER_FLAG_MARKED)) { DBG ("header 0x%02X was mandatory but not marked", i); return FALSE; } /* * Here we check for header positions. This function assumes that * headers marked with PRESET_POS are in the beginning of the message * and follow the same order as given in the va_arg list. The headers * marked this way have to be contiguous. */ for (i = 0; i < __MMS_HEADER_MAX + 1; i++) { int check_flags = HEADER_FLAG_PRESET_POS | HEADER_FLAG_MARKED; int expected_pos = 1; if ((entries[i].flags & check_flags) != check_flags) continue; va_start (args, orig_header); header = orig_header; while (header != MMS_HEADER_INVALID && header != i) { va_arg (args, int); va_arg (args, void *); if (entries[header].flags & HEADER_FLAG_MARKED) expected_pos += 1; header = va_arg (args, enum mms_header); } va_end (args); if (entries[i].pos != expected_pos) { DBG ("header 0x%02X was in position 0x%02X but expected in position 0x%02X", i, entries[i].pos, expected_pos); return FALSE; } } return TRUE; } static gboolean decode_delivery_ind (struct wsp_header_iter *iter, struct mms_message *out) { return mms_parse_headers (iter, MMS_HEADER_MMS_VERSION, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->version, MMS_HEADER_MESSAGE_ID, HEADER_FLAG_MANDATORY, &out->di.msgid, MMS_HEADER_TO, HEADER_FLAG_MANDATORY | HEADER_FLAG_ALLOW_MULTI, &out->di.to, MMS_HEADER_DATE, HEADER_FLAG_MANDATORY, &out->di.date, MMS_HEADER_STATUS, HEADER_FLAG_MANDATORY, &out->di.dr_status, MMS_HEADER_INVALID); } static gboolean decode_notification_ind (struct wsp_header_iter *iter, struct mms_message *out) { return mms_parse_headers (iter, MMS_HEADER_TRANSACTION_ID, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->transaction_id, MMS_HEADER_MMS_VERSION, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->version, MMS_HEADER_FROM, 0, &out->ni.from, MMS_HEADER_SUBJECT, 0, &out->ni.subject, MMS_HEADER_MESSAGE_CLASS, HEADER_FLAG_MANDATORY, &out->ni.cls, MMS_HEADER_MESSAGE_SIZE, HEADER_FLAG_MANDATORY, &out->ni.size, MMS_HEADER_EXPIRY, HEADER_FLAG_MANDATORY, &out->ni.expiry, MMS_HEADER_CONTENT_LOCATION, HEADER_FLAG_MANDATORY, &out->ni.location, MMS_HEADER_INVALID); } static const char * decode_attachment_charset (const unsigned char *pdu, unsigned int len) { struct wsp_parameter_iter iter; struct wsp_parameter param; wsp_parameter_iter_init (&iter, pdu, len); while (wsp_parameter_iter_next (&iter, ¶m)) if (param.type == WSP_PARAMETER_TYPE_CHARSET) return param.text; return NULL; } static gboolean extract_content_id (struct wsp_header_iter *iter, void *user) { char **out = user; const unsigned char *p; unsigned int l; const char *text; p = wsp_header_iter_get_val (iter); l = wsp_header_iter_get_val_len (iter); /* * Some MMSes do not encode a filename for the attachment. If this happens, * the value of iter will be empty. Rather than make this a chat applicaton's * problem, I am adding a random filename here. */ if (l == 0) { DBG ("Extracted content ID is empty, manually adding random id..."); *out = g_uuid_string_random (); } else { if (wsp_header_iter_get_val_type (iter) != WSP_VALUE_TYPE_TEXT) return FALSE; text = wsp_decode_quoted_string (p, l, NULL); if (text == NULL) return FALSE; *out = g_strdup (text); } DBG ("extracted content-id %s\n", *out); return TRUE; } static gboolean attachment_parse_headers (struct wsp_header_iter *iter, struct mms_attachment *part) { while (wsp_header_iter_next (iter)) { const unsigned char *hdr = wsp_header_iter_get_hdr (iter); unsigned char h; /* Skip application headers */ if (wsp_header_iter_get_hdr_type (iter) != WSP_HEADER_TYPE_WELL_KNOWN) continue; h = hdr[0] & 0x7f; switch (h) { case MMS_PART_HEADER_CONTENT_ID: if (extract_content_id (iter, &part->content_id) == FALSE) return FALSE; break; case MMS_PART_HEADER_CONTENT_LOCATION: break; default: break; } } return TRUE; } static void free_attachment (gpointer data, gpointer user_data) { struct mms_attachment *attach = data; g_free (attach->content_type); g_free (attach->content_id); g_free (attach); } static gboolean mms_parse_attachments (struct wsp_header_iter *iter, struct mms_message *out) { struct wsp_multipart_iter mi; const void *ct; unsigned int ct_len; unsigned int consumed; if (wsp_multipart_iter_init (&mi, iter, &ct, &ct_len) == FALSE) return FALSE; while (wsp_multipart_iter_next (&mi) == TRUE) { struct mms_attachment *part; struct wsp_header_iter hi; const void *mimetype; const char *charset; ct = wsp_multipart_iter_get_content_type (&mi); ct_len = wsp_multipart_iter_get_content_type_len (&mi); if (wsp_decode_content_type (ct, ct_len, &mimetype, &consumed, NULL) == FALSE) return FALSE; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpointer-arith" charset = decode_attachment_charset (ct + consumed, ct_len - consumed); #pragma GCC diagnostic pop wsp_header_iter_init (&hi, wsp_multipart_iter_get_hdr (&mi), wsp_multipart_iter_get_hdr_len (&mi), 0); part = g_try_new0 (struct mms_attachment, 1); if (part == NULL) return FALSE; if (attachment_parse_headers (&hi, part) == FALSE) { free_attachment (part, NULL); return FALSE; } if (wsp_header_iter_at_end (&hi) == FALSE) { free_attachment (part, NULL); return FALSE; } if (charset == NULL) part->content_type = g_strdup (mimetype); else part->content_type = g_strconcat (mimetype, ";charset=", charset, NULL); part->length = wsp_multipart_iter_get_body_len (&mi); part->offset = (const unsigned char *) wsp_multipart_iter_get_body (&mi) - wsp_header_iter_get_pdu (iter); out->attachments = g_slist_prepend (out->attachments, part); } if (wsp_multipart_iter_close (&mi, iter) == FALSE) return FALSE; out->attachments = g_slist_reverse (out->attachments); return TRUE; } static gboolean decode_retrieve_conf (struct wsp_header_iter *iter, struct mms_message *out) { if (mms_parse_headers (iter, MMS_HEADER_TRANSACTION_ID, HEADER_FLAG_PRESET_POS, &out->transaction_id, MMS_HEADER_MMS_VERSION, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->version, MMS_HEADER_FROM, 0, &out->rc.from, MMS_HEADER_TO, HEADER_FLAG_ALLOW_MULTI, &out->rc.to, MMS_HEADER_SUBJECT, 0, &out->rc.subject, MMS_HEADER_MESSAGE_CLASS, 0, &out->rc.cls, MMS_HEADER_PRIORITY, 0, &out->rc.priority, MMS_HEADER_MESSAGE_ID, 0, &out->rc.msgid, MMS_HEADER_DATE, HEADER_FLAG_MANDATORY, &out->rc.date, MMS_HEADER_INVALID) == FALSE) return FALSE; if (wsp_header_iter_at_end (iter) == TRUE) return TRUE; if (wsp_header_iter_is_multipart (iter) == FALSE) return FALSE; if (mms_parse_attachments (iter, out) == FALSE) return FALSE; if (wsp_header_iter_at_end (iter) == FALSE) return FALSE; return TRUE; } static gboolean decode_send_conf (struct wsp_header_iter *iter, struct mms_message *out) { return mms_parse_headers (iter, MMS_HEADER_TRANSACTION_ID, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->transaction_id, MMS_HEADER_MMS_VERSION, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->version, MMS_HEADER_RESPONSE_STATUS, HEADER_FLAG_MANDATORY, &out->sc.rsp_status, MMS_HEADER_MESSAGE_ID, 0, &out->sc.msgid, MMS_HEADER_INVALID); } static gboolean decode_send_req (struct wsp_header_iter *iter, struct mms_message *out) { if (mms_parse_headers (iter, MMS_HEADER_TRANSACTION_ID, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->transaction_id, MMS_HEADER_MMS_VERSION, HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, &out->version, MMS_HEADER_TO, HEADER_FLAG_ALLOW_MULTI, &out->sr.to, MMS_HEADER_SUBJECT, 0, &out->sr.subject, MMS_HEADER_INVALID) == FALSE) return FALSE; if (wsp_header_iter_at_end (iter) == TRUE) return TRUE; if (wsp_header_iter_is_multipart (iter) == FALSE) return FALSE; if (mms_parse_attachments (iter, out) == FALSE) return FALSE; if (wsp_header_iter_at_end (iter) == FALSE) return FALSE; return TRUE; } #define CHECK_WELL_KNOWN_HDR(hdr) \ if (wsp_header_iter_next (&iter) == FALSE) \ return FALSE; \ \ if (wsp_header_iter_get_hdr_type (&iter) != \ WSP_HEADER_TYPE_WELL_KNOWN) \ return FALSE; \ \ p = wsp_header_iter_get_hdr (&iter); \ \ if ((p[0] & 0x7f) != hdr) \ return FALSE \ gboolean mms_message_decode (const unsigned char *pdu, unsigned int len, struct mms_message *out) { unsigned int flags = 0; struct wsp_header_iter iter; const unsigned char *p; unsigned char octet; memset (out, 0, sizeof(*out)); flags |= WSP_HEADER_ITER_FLAG_REJECT_CP; flags |= WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART; wsp_header_iter_init (&iter, pdu, len, flags); DBG ("about to check well known"); CHECK_WELL_KNOWN_HDR (MMS_HEADER_MESSAGE_TYPE); DBG ("about to extract short"); if (extract_short (&iter, &octet) == FALSE) return FALSE; DBG ("octet %u", octet); if (octet < MMS_MESSAGE_TYPE_SEND_REQ || octet > MMS_MESSAGE_TYPE_DELIVERY_IND) return FALSE; out->type = octet; switch (out->type) { case MMS_MESSAGE_TYPE_SEND_REQ: DBG ("MMS_MESSAGE_TYPE_SEND_REQ"); return decode_send_req (&iter, out); case MMS_MESSAGE_TYPE_SEND_CONF: DBG ("MMS_MESSAGE_TYPE_SEND_CONF"); return decode_send_conf (&iter, out); case MMS_MESSAGE_TYPE_NOTIFICATION_IND: DBG ("MMS_MESSAGE_TYPE_NOTIFICATION_IND"); return decode_notification_ind (&iter, out); case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: DBG ("MMS_MESSAGE_TYPE_NOTIFYRESP_IND"); DBG ("Do not know how to decode"); return FALSE; case MMS_MESSAGE_TYPE_RETRIEVE_CONF: DBG ("MMS_MESSAGE_TYPE_RETRIEVE_CONF"); return decode_retrieve_conf (&iter, out); case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: DBG ("MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND"); DBG ("Do not know how to decode"); return FALSE; case MMS_MESSAGE_TYPE_DELIVERY_IND: DBG ("MMS_MESSAGE_TYPE_DELIVERY_IND"); return decode_delivery_ind (&iter, out); default: g_warning ("Unhandled case"); return FALSE; } return FALSE; } void mms_message_free (struct mms_message *msg) { switch (msg->type) { case MMS_MESSAGE_TYPE_SEND_REQ: g_free (msg->sr.to); g_free (msg->sr.content_type); g_free (msg->sr.datestamp); g_free (msg->sr.subject); g_free (msg->sr.delivery_recipients); break; case MMS_MESSAGE_TYPE_SEND_CONF: g_free (msg->sc.msgid); break; case MMS_MESSAGE_TYPE_NOTIFICATION_IND: g_free (msg->ni.from); g_free (msg->ni.subject); g_free (msg->ni.cls); g_free (msg->ni.location); break; case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: break; case MMS_MESSAGE_TYPE_RETRIEVE_CONF: g_free (msg->rc.from); g_free (msg->rc.to); g_free (msg->rc.subject); g_free (msg->rc.cls); g_free (msg->rc.priority); g_free (msg->rc.msgid); g_free (msg->rc.datestamp); break; case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: break; case MMS_MESSAGE_TYPE_DELIVERY_IND: g_free (msg->di.msgid); g_free (msg->di.to); break; default: g_warning ("Unhandled case"); break; } g_free (msg->uuid); g_free (msg->path); g_free (msg->transaction_id); if (msg->attachments != NULL) { g_slist_foreach (msg->attachments, free_attachment, NULL); g_slist_free (msg->attachments); } g_free (msg); } static void fb_init (struct file_buffer *fb, int fd) { fb->size = 0; fb->fsize = 0; fb->fd = fd; } static gboolean fb_flush (struct file_buffer *fb) { unsigned int size; ssize_t len; if (fb->size == 0) return TRUE; len = write (fb->fd, fb->buf, fb->size); if (len < 0) return FALSE; size = len; if (size != fb->size) return FALSE; fb->fsize += size; fb->size = 0; return TRUE; } static unsigned int fb_get_file_size (struct file_buffer *fb) { return fb->fsize + fb->size; } static void * fb_request (struct file_buffer *fb, unsigned int count) { if (fb->size + count < FB_SIZE) { void *ptr = fb->buf + fb->size; fb->size += count; return ptr; } if (fb_flush (fb) == FALSE) return NULL; if (count > FB_SIZE) return NULL; fb->size = count; return fb->buf; } static void * fb_request_field (struct file_buffer *fb, unsigned char token, unsigned int len) { unsigned char *ptr; ptr = fb_request (fb, len + 1); if (ptr == NULL) return NULL; ptr[0] = token | 0x80; return ptr + 1; } static gboolean fb_copy (struct file_buffer *fb, const void *buf, unsigned int c) { unsigned int written; ssize_t len; if (fb_flush (fb) == FALSE) return FALSE; len = TFR (write (fb->fd, buf, c)); if (len < 0) return FALSE; written = len; if (written != c) return FALSE; fb->fsize += written; return TRUE; } static gboolean fb_put_value_length (struct file_buffer *fb, unsigned int val) { unsigned int count; if (fb->size + MAX_ENC_VALUE_BYTES > FB_SIZE) if (fb_flush (fb) == FALSE) return FALSE; if (wsp_encode_value_length (val, fb->buf + fb->size, FB_SIZE - fb->size, &count) == FALSE) return FALSE; fb->size += count; return TRUE; } static gboolean fb_put_uintvar (struct file_buffer *fb, unsigned int val) { unsigned int count; if (fb->size + MAX_ENC_VALUE_BYTES > FB_SIZE) if (fb_flush (fb) == FALSE) return FALSE; if (wsp_encode_uintvar (val, fb->buf + fb->size, FB_SIZE - fb->size, &count) == FALSE) return FALSE; fb->size += count; return TRUE; } static gboolean encode_short (struct file_buffer *fb, enum mms_header header, void *user) { char *ptr; unsigned int *wk = user; ptr = fb_request_field (fb, header, 1); if (ptr == NULL) return FALSE; *ptr = *wk | 0x80; return TRUE; } static gboolean encode_from (struct file_buffer *fb, enum mms_header header, void *user) { char *ptr; char **text = user; if (strlen (*text) > 0) return FALSE; /* From: header token + value length + Insert-address-token */ ptr = fb_request_field (fb, header, 2); if (ptr == NULL) return FALSE; ptr[0] = 1; ptr[1] = 129; return TRUE; } static gboolean encode_text (struct file_buffer *fb, enum mms_header header, void *user) { char *ptr; char **text = user; unsigned int len; len = strlen (*text) + 1; //Do not encode anything if the test is empty if (len == 1) return TRUE; ptr = fb_request_field (fb, header, len); if (ptr == NULL) return FALSE; strcpy (ptr, *text); return TRUE; } static gboolean encode_quoted_string (struct file_buffer *fb, enum mms_header header, void *user) { char *ptr; char **text = user; unsigned int len; len = strlen (*text) + 1; ptr = fb_request_field (fb, header, len + 3); if (ptr == NULL) return FALSE; ptr[0] = '"'; ptr[1] = '<'; strcpy (ptr + 2, *text); ptr[len + 1] = '>'; ptr[len + 2] = '\0'; return TRUE; } static gboolean encode_text_array_element (struct file_buffer *fb, enum mms_header header, void *user) { char **text = user; char **tos; int i; tos = g_strsplit (*text, ",", 0); for (i = 0; tos[i] != NULL; i++) if (encode_text (fb, header, &tos[i]) == FALSE) { g_strfreev (tos); return FALSE; } g_strfreev (tos); return TRUE; } static gboolean encode_content_type (struct file_buffer *fb, enum mms_header header, void *user) { char *ptr; char **hdr = user; unsigned int len; unsigned int ct; unsigned int ct_len; unsigned int type_len; unsigned int start_len; const char *ct_str; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wuninitialized" const char *uninitialized_var (type); const char *uninitialized_var (start); #pragma GCC diagnostic pop struct wsp_text_header_iter iter; if (wsp_text_header_iter_init (&iter, *hdr) == FALSE) return FALSE; if (g_ascii_strcasecmp ("Content-Type", wsp_text_header_iter_get_key (&iter)) != 0) return FALSE; ct_str = wsp_text_header_iter_get_value (&iter); if (wsp_get_well_known_content_type (ct_str, &ct) == TRUE) ct_len = 1; else ct_len = strlen (ct_str) + 1; len = ct_len; type_len = 0; start_len = 0; while (wsp_text_header_iter_param_next (&iter) == TRUE) { if (g_ascii_strcasecmp ("type", wsp_text_header_iter_get_key (&iter)) == 0) { type = wsp_text_header_iter_get_value (&iter); type_len = strlen (type) + 1; len += 1 + type_len; } else if (g_ascii_strcasecmp ("start", wsp_text_header_iter_get_key (&iter)) == 0) { start = wsp_text_header_iter_get_value (&iter); start_len = strlen (start) + 1; len += 1 + start_len; } } if (len == 1) return encode_short (fb, header, &ct); ptr = fb_request (fb, 1); if (ptr == NULL) return FALSE; *ptr = header | 0x80; /* Encode content type value length */ if (fb_put_value_length (fb, len) == FALSE) return FALSE; /* Encode content type including parameters */ ptr = fb_request (fb, ct_len); if (ptr == NULL) return FALSE; if (ct_len == 1) *ptr = ct | 0x80; else strcpy (ptr, ct_str); if (type_len > 0) { ptr = fb_request_field (fb, WSP_PARAMETER_TYPE_CONTENT_TYPE, type_len); if (ptr == NULL) return FALSE; strcpy (ptr, type); } if (start_len > 0) { ptr = fb_request_field (fb, WSP_PARAMETER_TYPE_START_DEFUNCT, start_len); if (ptr == NULL) return FALSE; strcpy (ptr, start); } return TRUE; } static header_encoder encoder_for_type (enum mms_header header) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" //Refer to enum mms_header if you need to add headers switch (header) { case MMS_HEADER_BCC: return NULL; case MMS_HEADER_CC: return NULL; case MMS_HEADER_CONTENT_LOCATION: return NULL; case MMS_HEADER_CONTENT_TYPE: return encode_content_type; case MMS_HEADER_DATE: return NULL; case MMS_HEADER_DELIVERY_REPORT: return encode_short; case MMS_HEADER_DELIVERY_TIME: return NULL; case MMS_HEADER_EXPIRY: return NULL; case MMS_HEADER_FROM: return encode_from; case MMS_HEADER_MESSAGE_CLASS: return NULL; case MMS_HEADER_MESSAGE_ID: return NULL; case MMS_HEADER_MESSAGE_TYPE: return encode_short; case MMS_HEADER_MMS_VERSION: return encode_short; case MMS_HEADER_MESSAGE_SIZE: return NULL; case MMS_HEADER_PRIORITY: return NULL; case MMS_HEADER_READ_REPLY: return NULL; case MMS_HEADER_REPORT_ALLOWED: return NULL; case MMS_HEADER_RESPONSE_STATUS: return NULL; case MMS_HEADER_RESPONSE_TEXT: return NULL; case MMS_HEADER_RETRIEVE_STATUS: return NULL; case MMS_HEADER_RETRIEVE_TEXT: return NULL; case MMS_HEADER_READ_STATUS: return NULL; case MMS_HEADER_SENDER_VISIBILITY: return NULL; case MMS_HEADER_STATUS: return encode_short; case MMS_HEADER_SUBJECT: return encode_text; case MMS_HEADER_TO: return encode_text_array_element; case MMS_HEADER_TRANSACTION_ID: return encode_text; case MMS_HEADER_INVALID: case __MMS_HEADER_MAX: default: return NULL; } #pragma GCC diagnostic pop return NULL; } static gboolean mms_encode_send_req_part_header (struct mms_attachment *part, struct file_buffer *fb) { char *ptr; unsigned int len; unsigned int ct; unsigned int ct_len; unsigned int cs_len; const char *ct_str; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wuninitialized" const char *uninitialized_var (cs_str); #pragma GCC diagnostic pop unsigned int ctp_len; unsigned int cid_len; unsigned char ctp_val[MAX_ENC_VALUE_BYTES]; unsigned char cs_val[MAX_ENC_VALUE_BYTES]; unsigned int cs; struct wsp_text_header_iter iter; /* * Compute Headers length: content-type [+ params] [+ content-id] * ex. : "Content-Type:text/plain; charset=us-ascii" */ if (wsp_text_header_iter_init (&iter, part->content_type) == FALSE) return FALSE; if (g_ascii_strcasecmp ("Content-Type", wsp_text_header_iter_get_key (&iter)) != 0) return FALSE; ct_str = wsp_text_header_iter_get_value (&iter); if (wsp_get_well_known_content_type (ct_str, &ct) == TRUE) ct_len = 1; else ct_len = strlen (ct_str) + 1; len = ct_len; cs_len = 0; while (wsp_text_header_iter_param_next (&iter) == TRUE) { const char *key = wsp_text_header_iter_get_key (&iter); if (g_ascii_strcasecmp ("charset", key) == 0) { cs_str = wsp_text_header_iter_get_value (&iter); if (cs_str == NULL) return FALSE; len += 1; if (wsp_get_well_known_charset (cs_str, &cs) == FALSE) return FALSE; if (wsp_encode_integer (cs, cs_val, MAX_ENC_VALUE_BYTES, &cs_len) == FALSE) return FALSE; len += cs_len; } } if (wsp_encode_value_length (len, ctp_val, MAX_ENC_VALUE_BYTES, &ctp_len) == FALSE) return FALSE; len += ctp_len; /* Compute content-id header length : token + (Quoted String) */ if (part->content_id != NULL) { cid_len = 1 + strlen (part->content_id) + 3 + 1; len += cid_len; } else cid_len = 0; /* Encode total headers length */ if (fb_put_uintvar (fb, len) == FALSE) return FALSE; /* Encode data length */ if (fb_put_uintvar (fb, part->length) == FALSE) return FALSE; /* Encode content-type */ ptr = fb_request (fb, ctp_len); if (ptr == NULL) return FALSE; memcpy (ptr, &ctp_val, ctp_len); ptr = fb_request (fb, ct_len); if (ptr == NULL) return FALSE; if (ct_len == 1) ptr[0] = ct | 0x80; else strcpy (ptr, ct_str); /* Encode "charset" param */ if (cs_len > 0) { ptr = fb_request_field (fb, WSP_PARAMETER_TYPE_CHARSET, cs_len); if (ptr == NULL) return FALSE; memcpy (ptr, &cs_val, cs_len); } /* Encode content-id */ if (part->content_id != NULL) if (encode_quoted_string (fb, MMS_PART_HEADER_CONTENT_ID, &part->content_id) == FALSE) return FALSE; return TRUE; } static gboolean mms_encode_send_req_part (struct mms_attachment *part, struct file_buffer *fb) { if (mms_encode_send_req_part_header (part, fb) == FALSE) return FALSE; part->offset = fb_get_file_size (fb); return fb_copy (fb, part->data, part->length); } static gboolean mms_encode_headers (struct file_buffer *fb, enum mms_header orig_header, ...) { va_list args; void *data; enum mms_header header; header_encoder encoder; va_start (args, orig_header); header = orig_header; while (header != MMS_HEADER_INVALID) { data = va_arg (args, void *); encoder = encoder_for_type (header); if (encoder == NULL) return FALSE; if (data && encoder (fb, header, data) == FALSE) return FALSE; header = va_arg (args, enum mms_header); } va_end (args); return TRUE; } static gboolean mms_encode_notify_resp_ind (struct mms_message *msg, struct file_buffer *fb) { //Order Matters when you encode headers! //Refer to: OMA-WAP-MMS-ENC-V1_1-20021030-C, Table 1 //Note that the order below matches the order in Table 1 if (mms_encode_headers (fb, MMS_HEADER_MESSAGE_TYPE, &msg->type, MMS_HEADER_TRANSACTION_ID, &msg->transaction_id, MMS_HEADER_MMS_VERSION, &msg->version, MMS_HEADER_STATUS, &msg->nri.notify_status, MMS_HEADER_INVALID) == FALSE) return FALSE; return fb_flush (fb); } static gboolean mms_encode_send_req (struct mms_message *msg, struct file_buffer *fb) { const char *empty_from = ""; GSList *item; enum mms_message_value_bool dr; if (msg->sr.dr == TRUE) dr = MMS_MESSAGE_VALUE_BOOL_YES; else dr = MMS_MESSAGE_VALUE_BOOL_NO; //Order Matters when you encode headers! //Refer to: OMA-WAP-MMS-ENC-V1_1-20021030-C, Table 1 //Note that the order below matches the order in Table 1 if (mms_encode_headers (fb, MMS_HEADER_MESSAGE_TYPE, &msg->type, MMS_HEADER_TRANSACTION_ID, &msg->transaction_id, MMS_HEADER_MMS_VERSION, &msg->version, MMS_HEADER_FROM, &empty_from, MMS_HEADER_TO, &msg->sr.to, MMS_HEADER_SUBJECT, &msg->sr.subject, MMS_HEADER_DELIVERY_REPORT, &dr, MMS_HEADER_CONTENT_TYPE, &msg->sr.content_type, MMS_HEADER_INVALID) == FALSE) return FALSE; if (msg->attachments == NULL) goto done; if (fb_put_uintvar (fb, g_slist_length (msg->attachments)) == FALSE) return FALSE; for (item = msg->attachments; item != NULL; item = g_slist_next (item)) if (mms_encode_send_req_part (item->data, fb) == FALSE) return FALSE; done: return fb_flush (fb); } gboolean mms_message_encode (struct mms_message *msg, int fd) { struct file_buffer fb; fb_init (&fb, fd); switch (msg->type) { case MMS_MESSAGE_TYPE_SEND_REQ: return mms_encode_send_req (msg, &fb); case MMS_MESSAGE_TYPE_SEND_CONF: case MMS_MESSAGE_TYPE_NOTIFICATION_IND: return FALSE; case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: return mms_encode_notify_resp_ind (msg, &fb); case MMS_MESSAGE_TYPE_RETRIEVE_CONF: case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: case MMS_MESSAGE_TYPE_DELIVERY_IND: return FALSE; default: g_warning ("Unhandled case"); return FALSE; } return FALSE; } const char * mms_message_status_get_string (enum mms_message_status status) { switch (status) { case MMS_MESSAGE_STATUS_DOWNLOADED: return "downloaded"; case MMS_MESSAGE_STATUS_RECEIVED: return "received"; case MMS_MESSAGE_STATUS_READ: return "read"; case MMS_MESSAGE_STATUS_SENT: return "sent"; case MMS_MESSAGE_STATUS_DRAFT: return "draft"; case MMS_MESSAGE_STATUS_DELIVERED: return "delivered"; case MMS_MESSAGE_STATUS_SENDING_FAILED: return "sending_failed"; default: g_warning ("Unhandled case"); return NULL; } return NULL; } const char * message_rsp_status_to_string ( enum mms_message_rsp_status status) { switch (status) { case MMS_MESSAGE_RSP_STATUS_OK: return "ok"; case MMS_MESSAGE_RSP_STATUS_ERR_UNSUPPORTED_MESSAGE: return "error-unsupported-message"; case MMS_MESSAGE_RSP_STATUS_ERR_TRANS_FAILURE: return "error-transient-failure"; case MMS_MESSAGE_RSP_STATUS_ERR_TRANS_NETWORK_PROBLEM: return "error-transient-network-problem"; case MMS_MESSAGE_RSP_STATUS_ERR_PERM_FAILURE: return "error-permanent-failure"; case MMS_MESSAGE_RSP_STATUS_ERR_PERM_SERVICE_DENIED: return "error-permanent-service-denied"; case MMS_MESSAGE_RSP_STATUS_ERR_PERM_MESSAGE_FORMAT_CORRUPT: return "error-permanent-message-format-corrupt"; case MMS_MESSAGE_RSP_STATUS_ERR_PERM_SENDING_ADDRESS_UNRESOLVED: return "error-permanent-sending-address-unresolved"; case MMS_MESSAGE_RSP_STATUS_ERR_PERM_CONTENT_NOT_ACCEPTED: return "error-permanent-content-not-accepted"; case MMS_MESSAGE_RSP_STATUS_ERR_PERM_LACK_OF_PREPAID: return "error-permanent-lack-of-prepaid"; default: g_warning ("Unhandled case"); return NULL; } return NULL; } // mms_message_create_smil() was primarily done by // sending myself texts from the Android AOSP Messaging app // and looking at how they encode their SMIL, while looking at: // https://www.w3.org/TR/SMIL20/ // to see what it all means. I am aware SMIL 2.0 is outdated, // but the MMS spec says to use SMIL 2.0. *shrug* // You will have to forgive me for mms_message_create_smil() // being fairly crude. char * mms_message_create_smil (GVariant *attachments) { GString *smil, *smil_body; gchar *smil_body_to_attach; gchar *smil_to_return; g_autoptr(GVariant) single_attachment = NULL; GVariantIter iter; gboolean contains_only_image_video_plaintext = TRUE; gboolean contains_image_or_video = FALSE; gboolean contains_text = FALSE; gboolean contains_only_text = TRUE; g_variant_iter_init (&iter, attachments); smil = g_string_new (""); } if (g_str_match_string ("video", mime_type, FALSE)) { contains_image_or_video = TRUE; g_string_append (smil_body, ""); } if (g_str_match_string ("text", mime_type, FALSE)) { if (g_str_match_string ("vcf", mime_type, FALSE)) { DBG ("This MMS has something other than images, videos, or plaintext"); contains_only_image_video_plaintext = FALSE; g_string_append (smil_body, ""); } else { DBG ("This MMS has text"); contains_text = TRUE; g_string_append (smil_body, ""); } } else { DBG ("This MMS has content other than text"); contains_only_text = FALSE; } if (g_str_match_string ("audio", mime_type, FALSE)) { g_string_append (smil_body, ""); } } smil_body_to_attach = g_string_free (smil_body, FALSE); if (contains_only_image_video_plaintext && !contains_only_text) { DBG ("This MMS only has Images, Videos, or plaintext"); g_string_append (smil, " width=\"100%\" height=\"100%\""); } g_string_append (smil, "/>"); if (contains_only_text) { DBG ("This MMS only has text"); g_string_append (smil, ""); } else if (contains_text && contains_image_or_video) { DBG ("This MMS has text with images or videos"); g_string_append (smil, ""); } else if (contains_image_or_video) { DBG ("This MMS has images or videos"); g_string_append (smil, ""); } g_string_append (smil, ""); g_string_append (smil, smil_body_to_attach); g_free (smil_body_to_attach); g_string_append (smil, ""); smil_to_return = g_string_free (smil, FALSE); return smil_to_return; } mmsd-2.6.0/src/mmsutil.h000066400000000000000000000141751456374454100151360ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * 2020, Anteater * * 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. * * 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 * */ enum mms_message_type { MMS_MESSAGE_TYPE_SEND_REQ = 128, MMS_MESSAGE_TYPE_SEND_CONF = 129, MMS_MESSAGE_TYPE_NOTIFICATION_IND = 130, MMS_MESSAGE_TYPE_NOTIFYRESP_IND = 131, MMS_MESSAGE_TYPE_RETRIEVE_CONF = 132, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND = 133, MMS_MESSAGE_TYPE_DELIVERY_IND = 134, }; enum mms_message_status { MMS_MESSAGE_STATUS_DOWNLOADED, MMS_MESSAGE_STATUS_RECEIVED, MMS_MESSAGE_STATUS_READ, MMS_MESSAGE_STATUS_SENT, MMS_MESSAGE_STATUS_SENDING_FAILED, MMS_MESSAGE_STATUS_DRAFT, MMS_MESSAGE_STATUS_DELIVERED }; enum mms_message_rsp_status { MMS_MESSAGE_RSP_STATUS_OK = 128, MMS_MESSAGE_RSP_STATUS_ERR_UNSUPPORTED_MESSAGE = 136, MMS_MESSAGE_RSP_STATUS_ERR_TRANS_FAILURE = 192, MMS_MESSAGE_RSP_STATUS_ERR_TRANS_NETWORK_PROBLEM = 195, MMS_MESSAGE_RSP_STATUS_ERR_PERM_FAILURE = 224, MMS_MESSAGE_RSP_STATUS_ERR_PERM_SERVICE_DENIED = 225, MMS_MESSAGE_RSP_STATUS_ERR_PERM_MESSAGE_FORMAT_CORRUPT = 226, MMS_MESSAGE_RSP_STATUS_ERR_PERM_SENDING_ADDRESS_UNRESOLVED = 227, MMS_MESSAGE_RSP_STATUS_ERR_PERM_CONTENT_NOT_ACCEPTED = 229, MMS_MESSAGE_RSP_STATUS_ERR_PERM_LACK_OF_PREPAID = 235, }; enum mms_message_retr_status { MMS_MESSAGE_RETR_STATUS_OK = 128, MMS_MESSAGE_RETR_STATUS_ERR_TRANS_MIN = 192, MMS_MESSAGE_RETR_STATUS_ERR_TRANS_FAILURE = 192, MMS_MESSAGE_RETR_STATUS_ERR_TRANS_MESSAGE_NOT_FOUND = 194, MMS_MESSAGE_RETR_STATUS_ERR_PERM_MIN = 224, MMS_MESSAGE_RETR_STATUS_ERR_PERM_FAILURE = 224, MMS_MESSAGE_RETR_STATUS_ERR_PERM_SERVICE_DENIED = 225, MMS_MESSAGE_RETR_STATUS_ERR_PERM_MESSAGE_NOT_FOUND = 226, MMS_MESSAGE_RETR_STATUS_ERR_PERM_CONTENT_UNSUPPORTED = 227, }; enum mms_message_read_status { MMS_MESSAGE_READ_STATUS_READ = 128, MMS_MESSAGE_READ_STATUS_DELETED_UNREAD = 129, }; enum mms_message_notify_status { MMS_MESSAGE_NOTIFY_STATUS_RETRIEVED = 129, MMS_MESSAGE_NOTIFY_STATUS_REJECTED = 130, MMS_MESSAGE_NOTIFY_STATUS_DEFERRED = 131, MMS_MESSAGE_NOTIFY_STATUS_UNRECOGNISED = 132, }; enum mms_message_delivery_status { MMS_MESSAGE_DELIVERY_STATUS_EXPIRED = 128, MMS_MESSAGE_DELIVERY_STATUS_RETRIEVED = 129, MMS_MESSAGE_DELIVERY_STATUS_REJECTED = 130, MMS_MESSAGE_DELIVERY_STATUS_DEFERRED = 131, MMS_MESSAGE_DELIVERY_STATUS_UNRECOGNISED = 132, MMS_MESSAGE_DELIVERY_STATUS_INDETERMINATE = 133, MMS_MESSAGE_DELIVERY_STATUS_FORWARDED = 134, MMS_MESSAGE_DELIVERY_STATUS_UNREACHABLE = 135, }; enum mms_message_sender_visibility { MMS_MESSAGE_SENDER_VISIBILITY_HIDE = 128, MMS_MESSAGE_SENDER_VISIBILITY_SHOW = 129, }; enum mms_message_value_bool { MMS_MESSAGE_VALUE_BOOL_YES = 128, MMS_MESSAGE_VALUE_BOOL_NO = 129, }; enum mms_message_version { MMS_MESSAGE_VERSION_1_0 = 0x90, MMS_MESSAGE_VERSION_1_1 = 0x91, MMS_MESSAGE_VERSION_1_2 = 0x92, MMS_MESSAGE_VERSION_1_3 = 0x93, }; struct mms_notification_ind { char *from; char *subject; char *cls; unsigned int size; time_t expiry; char *location; }; struct mms_retrieve_conf { enum mms_message_status status; char *from; char *to; char *subject; char *cls; char *priority; char *msgid; time_t date; char *datestamp; }; struct mms_send_req { enum mms_message_status status; char *to; char *subject; time_t date; char *datestamp; char *content_type; gboolean dr; char *delivery_recipients; }; struct mms_send_conf { enum mms_message_rsp_status rsp_status; char *msgid; }; struct mms_notification_resp_ind { enum mms_message_notify_status notify_status; }; struct mms_delivery_ind { enum mms_message_delivery_status dr_status; char *msgid; char *to; time_t date; }; struct mms_attachment { unsigned char *data; size_t offset; size_t length; char *content_type; char *content_id; }; struct mms_message { enum mms_message_type type; char *uuid; char *path; char *transaction_id; guint message_registration_id; unsigned char version; GSList *attachments; union { struct mms_notification_ind ni; struct mms_retrieve_conf rc; struct mms_send_req sr; struct mms_send_conf sc; struct mms_notification_resp_ind nri; struct mms_delivery_ind di; }; }; char *mms_content_type_get_param_value (const char *content_type, const char *param_name); gboolean mms_message_decode (const unsigned char *pdu, unsigned int len, struct mms_message *out); gboolean mms_message_encode (struct mms_message *msg, int fd); void mms_message_free (struct mms_message *msg); const char *mms_message_status_get_string (enum mms_message_status status); char *mms_message_create_smil (GVariant *attachments); const char *message_rsp_status_to_string (enum mms_message_rsp_status status); mmsd-2.6.0/src/phone-utils.cpp000066400000000000000000000141571456374454100162460ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2012, 2013 Intel Corporation * 2021, Chris Talbot * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "phone-utils.h" using i18n::phonenumbers::PhoneNumber; using i18n::phonenumbers::PhoneNumberUtil; struct EPhoneNumber { PhoneNumber priv; }; typedef enum { E_PHONE_NUMBER_FORMAT_E164, E_PHONE_NUMBER_FORMAT_INTERNATIONAL, E_PHONE_NUMBER_FORMAT_NATIONAL, E_PHONE_NUMBER_FORMAT_RFC3966 } EPhoneNumberFormat; static PhoneNumberUtil * e_phone_number_util_get_instance (void) { static PhoneNumberUtil *instance = NULL; if (g_once_init_enter (&instance)) { /* FIXME: Ideally PhoneNumberUtil would not be a singleton, * so that we could safely tweak its attributes without * influencing other users of the library. */ PhoneNumberUtil *new_instance = PhoneNumberUtil::GetInstance (); /* Disable all logging: libphonenumber is pretty verbose. */ new_instance->SetLogger (new i18n::phonenumbers::NullLogger); g_once_init_leave (&instance, new_instance); } return instance; } /* * phone_utils_is_valid is a bit smarter on if a phone number is valid. Here * I just want to make sure that something like an email isn't coming through * like a phone number */ gboolean phone_utils_simple_is_valid (const char *number) { if (strspn (number, "+()- 0123456789") != strlen (number)) return false; return true; } gboolean phone_utils_is_valid (const char *number, const char *country_code) { PhoneNumberUtil *util = PhoneNumberUtil::GetInstance (); PhoneNumber phone_number; if (!number || !*number || !country_code || strlen (country_code) != 2) return FALSE; if (util->Parse (number, country_code, &phone_number) == PhoneNumberUtil::NO_PARSING_ERROR) return util->IsValidNumber (phone_number); return FALSE; } static bool _phone_utils_cxx_parse (const std::string &phone_number, const std::string ®ion, PhoneNumber *parsed_number) { const PhoneNumberUtil::ErrorType err = e_phone_number_util_get_instance ()->Parse ( phone_number, region, parsed_number); if (err != PhoneNumberUtil::NO_PARSING_ERROR) { return false; } return true; } static char * _phone_utils_cxx_to_string_e164 (const EPhoneNumber *phone_number) { g_return_val_if_fail (NULL != phone_number, NULL); std::string formatted_number; e_phone_number_util_get_instance ()->Format (phone_number->priv, static_cast (E_PHONE_NUMBER_FORMAT_E164), &formatted_number); if (!formatted_number.empty ()) return g_strdup (formatted_number.c_str ()); return NULL; } static char * _phone_utils_cxx_to_string_national (const EPhoneNumber *phone_number) { g_return_val_if_fail (NULL != phone_number, NULL); std::string formatted_number; e_phone_number_util_get_instance ()->Format (phone_number->priv, static_cast (E_PHONE_NUMBER_FORMAT_NATIONAL), &formatted_number); if (!formatted_number.empty ()) return g_strdup (formatted_number.c_str ()); return NULL; } static char * phone_utils_format (const char *phone_number, const char *region_code, gboolean national) { char *returned_string; EPhoneNumber *intermediate; if (!phone_number || !*phone_number || !region_code || strlen (region_code) != 2) return NULL; std::unique_ptr parsed_number(new EPhoneNumber); if (!_phone_utils_cxx_parse ( phone_number, region_code, &parsed_number->priv)) return NULL; intermediate = parsed_number.release (); if (national) returned_string = _phone_utils_cxx_to_string_national (intermediate); else returned_string = _phone_utils_cxx_to_string_e164 (intermediate); delete intermediate; return returned_string; } /* * phone_utils_format_number_e164() does two things: * * - To see if a number is valid (this does this with return_original_number set to FALSE), and * - To make sure that the valid number is in the E.164 format. * * If you see that return_original_number set to FALSE, the caller is checking * if the number is valid. If the number is valid, the number is returned in * the e.164 format. * * If return_original_number is TRUE, then the caller does not care if the * number is a real number (there are cases like one can send an email to * an MMS, so the "number" could be "foo@bar.com", and I don't want this * function to mess that up) and just wants to make sure that if there * is a number, it is properly formatted. * * No matter what, the string is newly allocated. * */ char *phone_utils_format_number_e164(const char *number, const char *country_code, gboolean return_original_number) { g_autofree char *temp_number; char *formatted_number; temp_number = g_strdup (number); /* There's a bug where a comma can leak in, let's just fix it here */ g_strdelimit (temp_number, ",", ' '); g_strstrip (temp_number); if (!phone_utils_simple_is_valid (temp_number)) goto error; formatted_number = phone_utils_format (temp_number, country_code, (*number != '+' && !phone_utils_is_valid (temp_number, country_code))); if (formatted_number == NULL) goto error; return formatted_number; error: if (return_original_number == FALSE) { return NULL; } else { return g_strdup(number); } } mmsd-2.6.0/src/phone-utils.h000066400000000000000000000025551456374454100157120ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2012,2013 Intel Corporation * 2021, Chris Talbot * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #pragma once #include G_BEGIN_DECLS gboolean phone_utils_is_valid (const char *number, const char *country_code); gboolean phone_utils_simple_is_valid (const char *number); gboolean phone_utils_is_possible (const char *number, const char *country_code); gchar *phone_utils_format_number_e164 (const char *number, const char *country_code, gboolean return_original_number); G_END_DECLS mmsd-2.6.0/src/plugin.c000066400000000000000000000062401456374454100147270ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include #include #include #include "mms.h" static GSList *plugins = NULL; struct mms_plugin { void *handle; struct mms_plugin_desc *desc; }; static gboolean add_plugin (void *handle, struct mms_plugin_desc *desc) { struct mms_plugin *plugin; if (desc->init == NULL) return FALSE; plugin = g_try_new0 (struct mms_plugin, 1); if (plugin == NULL) return FALSE; plugin->handle = handle; plugin->desc = desc; if (desc->init () < 0) { g_free (plugin); return FALSE; } plugins = g_slist_append (plugins, plugin); DBG ("Plugin %s loaded", desc->name); return TRUE; } #include "builtin.h" int __mms_plugin_init (void) { GDir *dir; const char *file; unsigned int i; if (strlen (PLUGINDIR) == 0) return -EINVAL; DBG (""); for (i = 0; __mms_builtin[i]; i++) add_plugin (NULL, __mms_builtin[i]); dir = g_dir_open (PLUGINDIR, 0, NULL); if (dir == NULL) return -EIO; while ((file = g_dir_read_name (dir)) != NULL) { struct mms_plugin_desc *desc; void *handle; char *filename; if (g_str_has_prefix (file, "lib") == TRUE || g_str_has_suffix (file, ".so") == FALSE) continue; filename = g_build_filename (PLUGINDIR, file, NULL); handle = dlopen (filename, RTLD_NOW); if (handle == NULL) { g_critical ("Can't load plugin %s: %s", filename, dlerror ()); g_free (filename); continue; } g_free (filename); desc = dlsym (handle, "mms_plugin_desc"); if (desc == NULL) { g_critical ("Can't load plugin description: %s", dlerror ()); dlclose (handle); continue; } if (add_plugin (handle, desc) == FALSE) dlclose (handle); } g_dir_close (dir); return 0; } void __mms_plugin_cleanup (void) { GSList *list; DBG (""); for (list = plugins; list; list = list->next) { struct mms_plugin *plugin = list->data; if (plugin->desc->exit) plugin->desc->exit (); if (plugin->handle != NULL) dlclose (plugin->handle); g_free (plugin); } g_slist_free (plugins); } mmsd-2.6.0/src/plugin.h000066400000000000000000000025731456374454100147410ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 mms_plugin_desc { const char *name; int (*init) (void); void (*exit) (void); }; #ifdef MMS_PLUGIN_BUILTIN #define MMS_PLUGIN_DEFINE(name, init, exit) \ struct mms_plugin_desc __mms_builtin_ ## name = { \ #name, init, exit \ }; #else #define MMS_PLUGIN_DEFINE(name, init, exit) \ extern struct mms_plugin_desc mms_plugin_desc \ __attribute__ ((visibility ("default"))); \ struct mms_plugin_desc mms_plugin_desc = { \ #name, init, exit \ }; #endif mmsd-2.6.0/src/service-providers.c000066400000000000000000000342731456374454100171130ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2009, Novell, Inc. * Author: Tambet Ingo (tambet@gmail.com). * 2009-2019, Red Hat, Inc. * 2012, Lanedo GmbH * 2021, Chris Talbot * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "service-providers.h" #include "mms.h" typedef enum { PARSER_TOPLEVEL = 0, PARSER_COUNTRY, PARSER_PROVIDER, PARSER_METHOD_GSM, PARSER_METHOD_GSM_APN, PARSER_METHOD_CDMA, PARSER_DONE, PARSER_ERROR } ParseContextState; typedef struct { char *mccmnc; GMarkupParseContext *ctx; char buffer[4096]; MmsdServiceProvidersCallback callback; gpointer user_data; char *text_buffer; ParseContextState state; gboolean mccmnc_matched; char *apn; char *apn_to_check; char *mmsc; char *proxy; char *mmssize; } ParseContext; /*****************************************************************************/ static void parser_toplevel_start (ParseContext *parse_context, const char *name, const char **attribute_names, const char **attribute_values) { int i; if (strcmp (name, "serviceproviders") == 0) { for (i = 0; attribute_names && attribute_names[i]; i++) if (strcmp (attribute_names[i], "format") == 0) if (strcmp (attribute_values[i], "2.0")) { g_warning ("%s: mobile broadband provider database format '%s'" " not supported.", __func__, attribute_values[i]); parse_context->state = PARSER_ERROR; break; } } else if (strcmp (name, "country") == 0) parse_context->state = PARSER_COUNTRY; } static void parser_country_start (ParseContext *parse_context, const char *name, const char **attribute_names, const char **attribute_values) { if (strcmp (name, "provider") == 0) parse_context->state = PARSER_PROVIDER; } static void parser_provider_start (ParseContext *parse_context, const char *name, const char **attribute_names, const char **attribute_values) { parse_context->mccmnc_matched = FALSE; if (strcmp (name, "gsm") == 0) parse_context->state = PARSER_METHOD_GSM; else if (strcmp (name, "cdma") == 0) parse_context->state = PARSER_METHOD_CDMA; } static void parser_gsm_start (ParseContext *parse_context, const char *name, const char **attribute_names, const char **attribute_values) { int i; if (strcmp (name, "network-id") == 0) { const char *mcc = NULL, *mnc = NULL; for (i = 0; attribute_names && attribute_names[i]; i++) { if (strcmp (attribute_names[i], "mcc") == 0) mcc = attribute_values[i]; else if (strcmp (attribute_names[i], "mnc") == 0) mnc = attribute_values[i]; if (mcc && strlen (mcc) && mnc && strlen (mnc)) { char *mccmnc = g_strdup_printf ("%s%s", mcc, mnc); if (strcmp (mccmnc, parse_context->mccmnc) == 0) parse_context->mccmnc_matched = TRUE; g_free (mccmnc); break; } } } else if (strcmp (name, "apn") == 0) { g_clear_pointer (&parse_context->apn, g_free); g_clear_pointer (&parse_context->mmsc, g_free); g_clear_pointer (&parse_context->proxy, g_free); for (i = 0; attribute_names && attribute_names[i]; i++) if (strcmp (attribute_names[i], "value") == 0) { parse_context->state = PARSER_METHOD_GSM_APN; parse_context->apn = g_strstrip (g_strdup (attribute_values[i])); break; } } } static void parser_start_element (GMarkupParseContext *context, const char *element_name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **error) { ParseContext *parse_context = user_data; g_clear_pointer (&parse_context->text_buffer, g_free); switch (parse_context->state) { case PARSER_TOPLEVEL: parser_toplevel_start (parse_context, element_name, attribute_names, attribute_values); break; case PARSER_COUNTRY: parser_country_start (parse_context, element_name, attribute_names, attribute_values); break; case PARSER_PROVIDER: parser_provider_start (parse_context, element_name, attribute_names, attribute_values); break; case PARSER_METHOD_GSM: parser_gsm_start (parse_context, element_name, attribute_names, attribute_values); break; case PARSER_METHOD_GSM_APN: break; case PARSER_METHOD_CDMA: break; case PARSER_ERROR: break; case PARSER_DONE: break; default: g_warning ("Got to undefined state"); break; } } static void parser_country_end (ParseContext *parse_context, const char *name) { if (strcmp (name, "country") == 0) { g_clear_pointer (&parse_context->text_buffer, g_free); parse_context->state = PARSER_TOPLEVEL; } } static void parser_provider_end (ParseContext *parse_context, const char *name) { if (strcmp (name, "provider") == 0) { g_clear_pointer (&parse_context->text_buffer, g_free); parse_context->state = PARSER_COUNTRY; } } static void parser_gsm_end (ParseContext *parse_context, const char *name) { if (strcmp (name, "gsm") == 0) { g_clear_pointer (&parse_context->text_buffer, g_free); parse_context->state = PARSER_PROVIDER; } } static void parser_gsm_apn_end (ParseContext *parse_context, const char *name) { if (strcmp (name, "mmsc") == 0) { g_clear_pointer (&parse_context->mmsc, g_free); parse_context->mmsc = g_steal_pointer (&parse_context->text_buffer); } else if (strcmp (name, "mmsproxy") == 0) { g_clear_pointer (&parse_context->proxy, g_free); parse_context->proxy = g_steal_pointer (&parse_context->text_buffer); } else if (strcmp (name, "mmsattachmentsize") == 0) { g_clear_pointer (&parse_context->mmssize, g_free); parse_context->mmssize = g_steal_pointer (&parse_context->text_buffer); } else if (strcmp (name, "apn") == 0) { g_clear_pointer (&parse_context->text_buffer, g_free); if (parse_context->mccmnc_matched && g_strcmp0 (parse_context->apn_to_check, parse_context->apn) == 0 && parse_context->mmsc != NULL) parse_context->state = PARSER_DONE; else parse_context->state = PARSER_METHOD_GSM; } } static void parser_cdma_end (ParseContext *parse_context, const char *name) { if (strcmp (name, "cdma") == 0) { g_clear_pointer (&parse_context->text_buffer, g_free); parse_context->state = PARSER_PROVIDER; } } static void parser_end_element (GMarkupParseContext *context, const char *element_name, gpointer user_data, GError **error) { ParseContext *parse_context = user_data; switch (parse_context->state) { case PARSER_TOPLEVEL: break; case PARSER_COUNTRY: parser_country_end (parse_context, element_name); break; case PARSER_PROVIDER: parser_provider_end (parse_context, element_name); break; case PARSER_METHOD_GSM: parser_gsm_end (parse_context, element_name); break; case PARSER_METHOD_GSM_APN: parser_gsm_apn_end (parse_context, element_name); break; case PARSER_METHOD_CDMA: parser_cdma_end (parse_context, element_name); break; case PARSER_ERROR: break; case PARSER_DONE: break; default: g_warning ("Got to undefined state"); break; } } static void parser_text (GMarkupParseContext *context, const char *text, gsize text_len, gpointer user_data, GError **error) { ParseContext *parse_context = user_data; g_free (parse_context->text_buffer); parse_context->text_buffer = g_strdup (text); } static const GMarkupParser parser = { .start_element = parser_start_element, .end_element = parser_end_element, .text = parser_text, .passthrough = NULL, .error = NULL, }; /*****************************************************************************/ static void finish_parse_context (ParseContext *parse_context, GError *error) { if (parse_context->callback) { if (error) parse_context ->callback (NULL, NULL, NULL, NULL, error, parse_context->user_data); else parse_context->callback (parse_context->apn, parse_context->mmsc, parse_context->proxy, parse_context->mmssize, error, parse_context->user_data); } g_free (parse_context->mccmnc); g_markup_parse_context_free (parse_context->ctx); g_free (parse_context->text_buffer); g_free (parse_context->apn); g_free (parse_context->apn_to_check); g_free (parse_context->mmsc); g_free (parse_context->proxy); g_slice_free (ParseContext, parse_context); } static void read_next_chunk (GInputStream *stream, ParseContext *parse_context); static void stream_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GInputStream *stream = G_INPUT_STREAM (source_object); ParseContext *parse_context = user_data; gssize len; GError *error = NULL; len = g_input_stream_read_finish (stream, res, &error); if (len == -1) { g_prefix_error (&error, "Error reading service provider database: "); finish_parse_context (parse_context, error); g_clear_error (&error); return; } if (len == 0) { g_set_error (&error, 1, 1, "Operator ID '%s' with APN '%s' not found in service provider database", parse_context->mccmnc, parse_context->apn_to_check); finish_parse_context (parse_context, error); g_clear_error (&error); return; } if (!g_markup_parse_context_parse (parse_context->ctx, parse_context->buffer, len, &error)) { g_prefix_error (&error, "Error parsing service provider database: "); finish_parse_context (parse_context, error); g_clear_error (&error); return; } if (parse_context->state == PARSER_DONE) { finish_parse_context (parse_context, NULL); return; } read_next_chunk (stream, parse_context); } static void read_next_chunk (GInputStream *stream, ParseContext *parse_context) { g_input_stream_read_async (stream, parse_context->buffer, sizeof(parse_context->buffer), G_PRIORITY_DEFAULT, NULL, stream_read_cb, parse_context); } static void file_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GFile *file = G_FILE (source_object); ParseContext *parse_context = user_data; GFileInputStream *stream; g_autoptr(GError) error = NULL; stream = g_file_read_finish (file, res, &error); if (!stream) { g_prefix_error (&error, "Error opening service provider database: "); finish_parse_context (parse_context, error); return; } read_next_chunk (G_INPUT_STREAM (stream), parse_context); g_object_unref (stream); } /*****************************************************************************/ void mmsd_service_providers_find_settings (const char *service_providers, const char *mccmnc, const char *apn, MmsdServiceProvidersCallback callback, gpointer user_data) { GFile *file; ParseContext *parse_context; parse_context = g_slice_new0 (ParseContext); parse_context->callback = callback; parse_context->mccmnc = g_strdup (mccmnc); parse_context->apn_to_check = g_strdup (apn); parse_context->user_data = user_data; parse_context->ctx = g_markup_parse_context_new (&parser, 0, parse_context, NULL); file = g_file_new_for_path (service_providers); DBG ("mccmnc: %s, apn: %s", parse_context->mccmnc, apn); g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, file_read_cb, parse_context); g_object_unref (file); } mmsd-2.6.0/src/service-providers.h000066400000000000000000000035751456374454100171210ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2009, Novell, Inc. * Author: Tambet Ingo (tambet@gmail.com). * 2009-2019, Red Hat, Inc. * 2012, Lanedo GmbH * 2021, Chris Talbot * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif typedef void (*MmsdServiceProvidersCallback)(const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data); void mmsd_service_providers_find_settings (const char *service_providers, const char *mccmnc, const char *apn, MmsdServiceProvidersCallback callback, gpointer user_data); mmsd-2.6.0/src/service.c000066400000000000000000004125361456374454100151020ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * 2021, Clayton Craft * 2020, Anteater * * 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. * * 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 * */ #define _XOPEN_SOURCE 700 /* See https://gitlab.gnome.org/GNOME/evolution-data-server/-/issues/332#note_1107764 */ #define EDS_DISABLE_DEPRECATED #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mmsutil.h" #include "mms.h" #include "dbus.h" #include "wsputil.h" #include "phone-utils.h" #define BEARER_SETUP_TIMEOUT 20 /* 20 seconds */ #define BEARER_IDLE_TIMEOUT 10 /* 10 seconds */ #define CHUNK_SIZE 2048 /* 2 Kib */ #define DEFAULT_CONTENT_TYPE "application/vnd.wap.mms-message" #define CT_MUTLIPART "Content-Type: \"application/vnd.wap.multipart." #define CT_TYPE ";type=\"application/smil\"" #define CT_START ";start=\"\"" #define CT_MULTIPART_RELATED CT_MUTLIPART "related\"" CT_TYPE CT_START #define CT_MULTIPART_MIXED CT_MUTLIPART "mixed\"" #define CONTENT_ID_SMIL "SMIL" #define CONTENT_TYPE_APP_SMIL "Content-Type: \"application/smil\";charset=utf-8" #define DEFAULT_MAX_ATTACHMENTS_NUMBER 25 #define MAX_ATTEMPTS 3 #define MMS_CONTENT_TYPE "application/vnd.wap.mms-message" #define SETTINGS_STORE "mms" #define SETTINGS_GROUP "Settings" #define RESOLVED_SERVICE "org.freedesktop.resolve1" #define RESOLVED_PATH "/org/freedesktop/resolve1" #define RESOLVED_MANAGER_INTERFACE RESOLVED_SERVICE ".Manager" static const char *ctl_chars = "\x01\x02\x03\x04\x05\x06\x07\x08\x0A" "\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14" "\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E" "\x1F\x7F"; static const char *sep_chars = "()<>@,;:\\\"/[]?={} \t"; static const char *const empty_subjects[] = { "NoSubject", "New MMS", "", NULL, }; struct mms_request; typedef gboolean (*mms_request_result_cb_t) (struct mms_request *request); struct mms_service { gint refcount; char *identity; char *path; gchar *mmsc; gchar *resolvers_ipv4_csv; gchar *resolvers_ipv6_csv; gchar *interface; char *request_path; gboolean proxy_active; char *country_code; char *own_number; char *apn; mms_service_bearer_handler_func_t bearer_handler; void *bearer_data; guint bearer_timeout; gboolean bearer_setup; gboolean bearer_active; GQueue *request_queue; SoupMessage *current_request_msg; SoupSession *web; GHashTable *messages; GKeyFile *settings; gboolean use_delivery_reports; int max_attach_total_size; int max_attachments; int auto_create_smil; int force_c_ares; GCancellable *cancel_current_msg; int notification_ind; }; enum mms_request_type { MMS_REQUEST_TYPE_GET, MMS_REQUEST_TYPE_POST, MMS_REQUEST_TYPE_POST_TMP }; struct mms_request { enum mms_request_type type; char *data_path; char *data_to_post_path; gchar *location; int fd; guint16 status; guint16 attempt; struct mms_service *service; gulong soupmessage_network_event_signal_id; mms_request_result_cb_t result_cb; struct mms_message *msg; }; enum mms_tx_rx_error { MMS_TX_RX_ERROR_UNKNOWN, MMS_TX_RX_ERROR_DNS, MMS_TX_RX_ERROR_HTTP }; static GList *service_list; static guint32 transaction_id_start = 0; gboolean global_debug = FALSE; guint service_registration_id; guint manager_registration_id; guint systemd_resolved_watcher_id; GDBusProxy *systemd_resolved_proxy; static const char *time_to_str (const time_t *t); void debug_print (const char *s, void *data); static void process_request_queue (struct mms_service *service); static void emit_msg_status_changed (const char *path, const char *new_status); static void append_message (const char *path, const struct mms_service *service, struct mms_message *msg, GVariantBuilder *message_builder); static void append_message_entry (char *path, const struct mms_service *service, struct mms_message *msg, GVariantBuilder *message_builder); static void append_properties (GVariantBuilder *service_builder, struct mms_service *service); static gboolean send_message_get_args (GVariant *parameters, struct mms_message *msg, struct mms_service *service); static void release_attachement_data (GSList *attach); static inline char *create_transaction_id (void); static struct mms_request *create_request (enum mms_request_type type, mms_request_result_cb_t result_cb, char *location, struct mms_service *service, struct mms_message *msg); static gboolean result_request_send_conf (struct mms_request *request); static const char *mms_address_to_string (char *mms_address); static void emit_message_added (const struct mms_service *service, struct mms_message *msg); static void mms_request_destroy (struct mms_request *request); static gboolean valid_content_type (char *ct); static gboolean result_request_retrieve_conf (struct mms_request *request); static gboolean result_request_notify_resp (struct mms_request *request); /* If the max_size is 0 or less, set the default */ void service_set_max_attach_size (struct mms_service *service, int max_size) { if (max_size <= 0) max_size = DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE; service->max_attach_total_size = max_size; DBG ("TotalMaxAttachmentSize is now set to %d!", service->max_attach_total_size); g_key_file_set_integer (service->settings, SETTINGS_GROUP, "TotalMaxAttachmentSize", service->max_attach_total_size); mms_settings_sync (service->identity, SETTINGS_STORE, service->settings); } int service_get_max_attach_size (struct mms_service *service) { return service->max_attach_total_size; } static void handle_method_call_service (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { if (g_strcmp0 (method_name, "GetMessages") == 0) { struct mms_service *service = user_data; GVariantBuilder messages_builder; GVariant *messages, *all_messages; GHashTableIter table_iter; gpointer key, value; guint i = 0; DBG ("Retrieving all Messages..."); if (g_hash_table_size (service->messages) == 0) { all_messages = g_variant_new ("(a(oa{sv}))", NULL); DBG ("No Messages!"); } else { g_variant_builder_init (&messages_builder, G_VARIANT_TYPE ("a(oa{sv})")); g_hash_table_iter_init (&table_iter, service->messages); while (g_hash_table_iter_next (&table_iter, &key, &value)) { i = i + 1; DBG ("On message %d!", i); g_variant_builder_open (&messages_builder, G_VARIANT_TYPE ("(oa{sv})")); append_message_entry (key, service, value, &messages_builder); g_variant_builder_close (&messages_builder); } DBG ("Messages total: %d", i); messages = g_variant_builder_end (&messages_builder); all_messages = g_variant_new ("(*)", messages); } g_dbus_method_invocation_return_value (invocation, all_messages); } else if (g_strcmp0 (method_name, "GetProperties") == 0) { struct mms_service *service = user_data; GVariantBuilder properties_builder; GVariant *properties, *all_properties; g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add_parsed (&properties_builder, "{'UseDeliveryReports', <%b>}", service->use_delivery_reports); g_variant_builder_add_parsed (&properties_builder, "{'AutoCreateSMIL', <%b>}", service->auto_create_smil); g_variant_builder_add_parsed (&properties_builder, "{'TotalMaxAttachmentSize', <%i>}", service->max_attach_total_size); g_variant_builder_add_parsed (&properties_builder, "{'MaxAttachments', <%i>}", service->max_attachments); g_variant_builder_add_parsed (&properties_builder, "{'NotificationInds', <%i>}", service->notification_ind); properties = g_variant_builder_end (&properties_builder); all_properties = g_variant_new ("(*)", properties); g_dbus_method_invocation_return_value (invocation, all_properties); } else if (g_strcmp0 (method_name, "SendMessage") == 0) { struct mms_message *msg; struct mms_service *service = user_data; struct mms_request *request; GVariant *messagepathvariant; GKeyFile *meta; const char *datestr; msg = g_new0 (struct mms_message, 1); if (msg == NULL) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Could not allocate memory for MMS!"); return; } msg->type = MMS_MESSAGE_TYPE_SEND_REQ; msg->version = MMS_MESSAGE_VERSION_1_3; msg->sr.status = MMS_MESSAGE_STATUS_DRAFT; msg->sr.dr = service->use_delivery_reports; time (&msg->sr.date); datestr = time_to_str (&msg->sr.date); g_free (msg->sr.datestamp); msg->sr.datestamp = g_strdup (datestr); if (send_message_get_args (parameters, msg, service) == FALSE) { DBG ("Invalid arguments"); release_attachement_data (msg->attachments); mms_message_free (msg); g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Invalid Arguments!"); return; } msg->transaction_id = create_transaction_id (); if (msg->transaction_id == NULL) { g_critical ("Error in create_transaction_id()"); release_attachement_data (msg->attachments); mms_message_free (msg); g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "There was an error sending the MMS!"); return; } request = create_request (MMS_REQUEST_TYPE_POST, result_request_send_conf, NULL, service, msg); if (request == NULL) { g_critical ("Error in create_request()"); release_attachement_data (msg->attachments); mms_message_free (msg); g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "There was an error sending the MMS!"); return; } /* Encode the MMS on the temp file */ if (mms_message_encode (msg, request->fd) == FALSE) { g_critical ("Error in mms_message_encode()"); unlink (request->data_path); mms_request_destroy (request); release_attachement_data (msg->attachments); mms_message_free (msg); g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "There was an error sending the MMS!"); return; } close (request->fd); request->fd = -1; /* Rename the temp file to the stored MMS file */ msg->uuid = mms_store_file (service->identity, request->data_path); if (msg->uuid == NULL) { g_critical ("Error in mms_store_file()"); unlink (request->data_path); mms_request_destroy (request); release_attachement_data (msg->attachments); mms_message_free (msg); g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "There was an error sending the MMS!"); return; } /* Remap the data_path file to the renamed file */ g_free (request->data_path); request->data_path = g_strdup_printf ("%s/.mms/%s/%s", g_get_home_dir (), service->identity, msg->uuid); meta = mms_store_meta_open (service->identity, msg->uuid); if (meta == NULL) { g_critical ("Error in mms_store_meta_open()"); unlink (request->data_path); mms_request_destroy (request); release_attachement_data (msg->attachments); mms_message_free (msg); g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "There was an error sending the MMS!"); return; } g_key_file_set_string (meta, "info", "date", msg->sr.datestamp); g_key_file_set_string (meta, "info", "state", "draft"); if (msg->sr.dr) { char **tos; int i; GString *to_concat = g_string_new (NULL); g_key_file_set_boolean (meta, "info", "delivery_report", TRUE); tos = g_strsplit (msg->sr.to, ",", 0); for (i = 0; tos[i] != NULL; i++) { g_autofree char *to = g_strdup (tos[i]); g_autofree char *formatted_to = NULL; mms_address_to_string (to); formatted_to = phone_utils_format_number_e164 (to, service->country_code, TRUE); to_concat = g_string_append (to_concat, formatted_to); to_concat = g_string_append (to_concat, ","); g_key_file_set_string (meta, "delivery_status", formatted_to, "none"); } to_concat = g_string_truncate (to_concat, (strlen (to_concat->str) - 1)); g_key_file_set_string (meta, "info", "delivery_recipients", to_concat->str); g_key_file_set_integer (meta, "info", "delivery_recipients_number", i); g_key_file_set_integer (meta, "delivery_status", "delivery_number_complete", 0); g_strfreev (tos); msg->sr.delivery_recipients = g_string_free (to_concat, FALSE); } else g_key_file_set_boolean (meta, "info", "delivery_report", FALSE); mms_store_meta_close (service->identity, msg->uuid, meta, TRUE); if (mms_message_register (service, msg) < 0) { g_critical ("Error in mms_message_register()"); unlink (request->data_path); mms_request_destroy (request); release_attachement_data (msg->attachments); mms_message_free (msg); g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "There was an error sending the MMS!"); return; } emit_message_added (service, msg); release_attachement_data (msg->attachments); /* Prioritize a message being sent */ g_queue_push_head (service->request_queue, request); activate_bearer (service); messagepathvariant = g_variant_new ("(o)", msg->path); g_dbus_method_invocation_return_value (invocation, messagepathvariant); } else if (g_strcmp0 (method_name, "SetProperty") == 0) { GVariant *variantstatus; gchar *dict; gboolean deliveryreports, autocreatesmil; gint max_size; struct mms_service *service = user_data; g_variant_get (parameters, "(sv)", &dict, &variantstatus); if (g_strcmp0 (dict, "UseDeliveryReports") == 0) { g_variant_get (variantstatus, "b", &deliveryreports); service->use_delivery_reports = deliveryreports; DBG ("Delivery Reports set to %d", deliveryreports); g_key_file_set_boolean (service->settings, SETTINGS_GROUP, "UseDeliveryReports", service->use_delivery_reports); g_dbus_method_invocation_return_value (invocation, NULL); } else if (g_strcmp0 (dict, "TotalMaxAttachmentSize") == 0) { g_variant_get (variantstatus, "i", &max_size); service_set_max_attach_size (service, max_size); g_dbus_method_invocation_return_value (invocation, NULL); } else if (g_strcmp0 (dict, "MaxAttachments") == 0) { g_variant_get (variantstatus, "i", &max_size); service->max_attachments = max_size; DBG ("MaxAttachments is now set to %d!", service->max_attachments); g_key_file_set_integer (service->settings, SETTINGS_GROUP, "MaxAttachments", service->max_attachments); g_dbus_method_invocation_return_value (invocation, NULL); } else if (g_strcmp0 (dict, "AutoCreateSMIL") == 0) { g_variant_get (variantstatus, "b", &autocreatesmil); service->auto_create_smil = autocreatesmil; DBG ("AutoCreateSMIL set to %d", autocreatesmil); g_key_file_set_boolean (service->settings, SETTINGS_GROUP, "AutoCreateSMIL", service->auto_create_smil); g_dbus_method_invocation_return_value (invocation, NULL); } else { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Cannot find the Property requested!"); return; } mms_settings_sync (service->identity, SETTINGS_STORE, service->settings); } else { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Cannot find the method requested!"); return; } } static const GDBusInterfaceVTable interface_vtable_service = { handle_method_call_service }; static void handle_method_call_manager (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { if (g_strcmp0 (method_name, "GetServices") == 0) { struct mms_service *service; GVariant *all_services; GList *l; DBG ("At Get Services Method Call"); if (service_list) { GVariantBuilder service_builder; GVariant *get_services; g_variant_builder_init (&service_builder, G_VARIANT_TYPE ("a(oa{sv})")); for (l = service_list; l != NULL; l = l->next) { service = l->data; g_variant_builder_open (&service_builder, G_VARIANT_TYPE ("(oa{sv})")); append_properties (&service_builder, service); g_variant_builder_close (&service_builder); } get_services = g_variant_builder_end (&service_builder); all_services = g_variant_new ("(*)", get_services); } else all_services = g_variant_new ("(a(oa{sv}))", NULL); g_dbus_method_invocation_return_value (invocation, all_services); } else { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Cannot find the method requested!"); return; } } static const GDBusInterfaceVTable interface_vtable_manager = { handle_method_call_manager }; static void handle_method_call_message (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { if (g_strcmp0 (method_name, "Delete") == 0) { struct mms_service *service = user_data; struct mms_message *mms; const char *path = object_path; g_autofree char *uuid = NULL; DBG ("Deleting Message path %s", path); mms = g_hash_table_lookup (service->messages, path); if (mms == NULL) g_dbus_method_invocation_return_dbus_error (invocation, MMS_MESSAGE_INTERFACE, "Cannot find the MMS to delete!"); uuid = g_strdup (mms->uuid); if (mms_message_unregister (service, path, mms->message_registration_id) < 0) g_dbus_method_invocation_return_dbus_error (invocation, MMS_MESSAGE_INTERFACE, "There was an error deleting the MMS!"); mms_store_remove (service->identity, uuid); DBG ("Successfully Deleted Message!"); g_dbus_method_invocation_return_value (invocation, NULL); } else if (g_strcmp0 (method_name, "MarkRead") == 0) { struct mms_service *service = user_data; struct mms_message *mms; const char *path = object_path; g_autofree char *state = NULL; GKeyFile *meta; DBG ("message path %s", path); mms = g_hash_table_lookup (service->messages, path); if (mms == NULL) g_dbus_method_invocation_return_dbus_error (invocation, MMS_MESSAGE_INTERFACE, "Cannot find this MMS!"); meta = mms_store_meta_open (service->identity, mms->uuid); state = g_key_file_get_string (meta, "info", "state", NULL); if (state == NULL) { mms_store_meta_close (service->identity, mms->uuid, meta, FALSE); g_dbus_method_invocation_return_dbus_error (invocation, MMS_MESSAGE_INTERFACE, "Cannot find this MMS. Was it Deleted?"); } if (strcmp (state, "received") != 0 && strcmp (state, "sent") != 0 && strcmp (state, "delivered") != 0) { mms_store_meta_close (service->identity, mms->uuid, meta, FALSE); g_dbus_method_invocation_return_dbus_error (invocation, MMS_MESSAGE_INTERFACE, "This MMS cannot be marked read!"); } g_key_file_set_boolean (meta, "info", "read", TRUE); mms->rc.status = MMS_MESSAGE_STATUS_READ; mms_store_meta_close (service->identity, mms->uuid, meta, TRUE); emit_msg_status_changed (path, "read"); g_dbus_method_invocation_return_value (invocation, NULL); } else { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Cannot find the method requested!"); return; } } static const GDBusInterfaceVTable interface_vtable_message = { handle_method_call_message }; static void mms_load_settings (struct mms_service *service) { GError *error; service->settings = mms_settings_open (service->identity, SETTINGS_STORE); if (service->settings == NULL) return; error = NULL; service->use_delivery_reports = g_key_file_get_boolean (service->settings, SETTINGS_GROUP, "UseDeliveryReports", &error); if (error) { g_error_free (error); service->use_delivery_reports = FALSE; g_key_file_set_boolean (service->settings, SETTINGS_GROUP, "UseDeliveryReports", service->use_delivery_reports); error = NULL; } service->max_attach_total_size = g_key_file_get_integer (service->settings, SETTINGS_GROUP, "TotalMaxAttachmentSize", &error); if (error) { g_error_free (error); service_set_max_attach_size (service, DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE); error = NULL; } DBG ("Maximum Attachment Total Size (in bytes): %d", service->max_attach_total_size); service->max_attachments = g_key_file_get_integer (service->settings, SETTINGS_GROUP, "MaxAttachments", &error); if (error) { g_error_free (error); service->max_attachments = DEFAULT_MAX_ATTACHMENTS_NUMBER; g_key_file_set_integer (service->settings, SETTINGS_GROUP, "MaxAttachments", service->max_attachments); error = NULL; } DBG ("Maximum Number of Attachments: %d", service->max_attachments); service->auto_create_smil = g_key_file_get_boolean (service->settings, SETTINGS_GROUP, "AutoCreateSMIL", &error); if (error) { g_error_free (error); service->auto_create_smil = FALSE; g_key_file_set_boolean (service->settings, SETTINGS_GROUP, "AutoCreateSMIL", service->auto_create_smil); error = NULL; } DBG ("AutoCreateSMIL is set to: %d", service->auto_create_smil); service->force_c_ares = g_key_file_get_boolean (service->settings, SETTINGS_GROUP, "ForceCAres", &error); if (error) { g_error_free (error); service->force_c_ares = TRUE; g_key_file_set_boolean (service->settings, SETTINGS_GROUP, "ForceCAres", service->force_c_ares); error = NULL; } DBG ("Force c-ares is set to: %d", service->force_c_ares); } static void mms_request_destroy (struct mms_request *request) { g_free (request->data_path); g_free (request->data_to_post_path); g_free (request->location); g_free (request); } static struct mms_message * mms_request_steal_message (struct mms_request *request) { struct mms_message *msg = request->msg; request->msg = NULL; return msg; } static void emit_msg_status_changed (const char *path, const char *new_status) { GDBusConnection *connection = mms_dbus_get_connection (); GVariant *changedproperty; g_autoptr(GError) error = NULL; DBG ("Emitting status of %s changed to %s", path, new_status); changedproperty = g_variant_new_parsed ("('status', <%s>)", new_status); g_dbus_connection_emit_signal (connection, NULL, path, MMS_MESSAGE_INTERFACE, "PropertyChanged", changedproperty, &error); if (error != NULL) { g_warning ("Error in Proxy call: %s\n", error->message); error = NULL; } } static gboolean valid_content_type (char *ct) { if (strlen (ct) == 0) return FALSE; if (strpbrk (ct, ctl_chars) != NULL) return FALSE; if (isspace (*ct) == TRUE) return FALSE; ct = strpbrk (ct, sep_chars); if (ct == NULL) return FALSE; if (ct[0] != '/') return FALSE; ct += 1; ct = strpbrk (ct, sep_chars); if (ct == NULL) return TRUE; return FALSE; } static gboolean mmap_file (const char *path, void **out_pdu, size_t *out_len) { struct stat st; int fd; fd = open (path, O_RDONLY); if (fd < 0) { g_critical ("Failed to open %s", path); return FALSE; } if (fstat (fd, &st) < 0) { g_critical ("Failed to stat %s", path); close (fd); return FALSE; } *out_pdu = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); close (fd); if (*out_pdu == MAP_FAILED) { g_critical ("Failed to mmap %s", path); return FALSE; } *out_len = st.st_size; return TRUE; } static const char * mms_address_to_string (char *mms_address) { unsigned int prefix_len; if (g_str_has_suffix (mms_address, "/TYPE=PLMN") == TRUE) { prefix_len = strlen (mms_address) - 10; mms_address[prefix_len] = '\0'; } return (const char *) mms_address; } static gboolean send_message_get_recipients (GVariant *recipients, struct mms_message *msg, struct mms_service *service) { GVariantIter iter; g_autoptr(GVariant) single_recipient = NULL; g_variant_iter_init (&iter, recipients); while ((single_recipient = g_variant_iter_next_value (&iter))) { const char *rec; g_autofree char *formatted_rec = NULL; char *tmp; rec = g_variant_get_string (single_recipient, NULL); formatted_rec = phone_utils_format_number_e164 (rec, service->country_code, FALSE); if (formatted_rec == NULL) return FALSE; if (msg->sr.to != NULL) { tmp = g_strconcat (msg->sr.to, ",", formatted_rec, "/TYPE=PLMN", NULL); if (tmp == NULL) return FALSE; g_free (msg->sr.to); msg->sr.to = tmp; } else msg->sr.to = g_strdup_printf ("%s/TYPE=PLMN", formatted_rec); } return TRUE; } static gboolean send_message_get_attachments (GVariant *attachments, struct mms_message *msg, struct mms_service *service) { gsize number_of_attachments; GVariantIter iter; g_autoptr(GVariant) single_attachment = NULL; int attach_total_size = 0; number_of_attachments = g_variant_iter_init (&iter, attachments); DBG ("number_of_attachments %ld", number_of_attachments); if (number_of_attachments > service->max_attachments) { DBG ("Error: Too many attachments!"); return FALSE; } while ((single_attachment = g_variant_iter_next_value (&iter))) { g_autofree char *content_id = NULL; g_autofree char *mime_type = NULL; g_autofree char *mime_type_test = NULL; g_autofree char *file_path = NULL; struct mms_attachment *attach; void *ptr; g_variant_get (single_attachment, "(sss)", &content_id, &mime_type, &file_path); DBG ("Content ID: %s, MIME Type: %s, File Path: %s", content_id, mime_type, file_path); if (valid_content_type (mime_type) == FALSE) return FALSE; /* * Android does not recognise the text/vcard nor the * text/vcalendar MIME type, but does recognize the * test/x-vCard and text/x-vCalendar MIME types. * this is a fix to maintain compatibility with that. */ mime_type_test = g_utf8_strdown (mime_type, -1); if (g_strcmp0 (mime_type_test, "text/vcard") == 0) { g_free (mime_type); mime_type = g_strdup ("text/x-vCard"); } if (g_strcmp0 (mime_type_test, "text/calendar") == 0) { g_free (mime_type); mime_type = g_strdup ("text/x-vCalendar"); } attach = g_try_new0 (struct mms_attachment, 1); if (attach == NULL) return FALSE; if (mmap_file (file_path, &ptr, &attach->length) == FALSE) return FALSE; attach_total_size = attach_total_size + attach->length; DBG ("Total attachment size: %d", attach_total_size); DBG ("Maximum Attachment Total Size (in bytes): %d", service->max_attach_total_size); if (attach_total_size > service->max_attach_total_size) { DBG ("Error: Total Attachment size too large!"); return FALSE; } attach->data = ptr; attach->content_id = g_strdup (content_id); if (g_str_has_prefix (mime_type, "text/") == TRUE) attach->content_type = g_strconcat ("Content-Type: \"", mime_type, "\";charset=utf-8", NULL); else attach->content_type = g_strconcat ("Content-Type: \"", mime_type, "\"", NULL); msg->attachments = g_slist_append (msg->attachments, attach); } return TRUE; } static gboolean send_message_get_args (GVariant *parameters, struct mms_message *msg, struct mms_service *service) { const char *smil_copy = NULL; g_autofree char *smil = NULL; g_autofree char *created_smil = NULL; g_autoptr(GVariant) recipients = NULL; g_autoptr(GVariant) options_container = NULL; g_autoptr(GVariant) options = NULL; g_autoptr(GVariant) attachments = NULL; recipients = g_variant_get_child_value (parameters, 0); if (send_message_get_recipients (recipients, msg, service) == FALSE) return FALSE; options_container = g_variant_get_child_value (parameters, 1); options = g_variant_get_child_value (options_container, 0); /* Only SMIL was sent as an option */ if (g_variant_check_format_string (options, "s", FALSE)) { smil_copy = g_variant_get_string (options, NULL); smil = g_strdup (smil_copy); msg->sr.subject = g_strdup (""); } else if (g_variant_check_format_string (options, "a{sv}", FALSE)) { GVariantDict dict; char *subject; gboolean delivery_report; g_variant_dict_init (&dict, options); if (g_variant_dict_lookup (&dict, "smil", "s", &smil_copy)) smil = g_strdup (smil_copy); else smil = g_strdup (""); if (g_variant_dict_lookup (&dict, "DeliveryReport", "b", &delivery_report)) msg->sr.dr = delivery_report; if (g_variant_dict_lookup (&dict, "Subject", "s", &subject)) msg->sr.subject = subject; else msg->sr.subject = g_strdup (""); g_variant_dict_clear (&dict); } else { g_critical ("This is an invalid g_variant format!"); return FALSE; } attachments = g_variant_get_child_value (parameters, 2); if (strlen (smil) > 0) created_smil = g_strdup (smil); else if (service->auto_create_smil) created_smil = mms_message_create_smil (attachments); else created_smil = g_strdup (""); if (strlen (created_smil) > 0) { struct mms_attachment *attach; DBG ("Attaching SMIL"); attach = g_try_new0 (struct mms_attachment, 1); if (attach == NULL) return FALSE; attach->content_id = g_strdup (CONTENT_ID_SMIL); attach->content_type = g_strdup (CONTENT_TYPE_APP_SMIL); attach->length = strlen (created_smil) + 1; // Use safer g_memdup2() if glib is 2.68.1 or higher #if GLIB_CHECK_VERSION (2, 68, 1) attach->data = g_memdup2 (created_smil, attach->length); #else // https://discourse.gnome.org/t/port-your-module-from-g-memdup-to-g-memdup2-now/5538 // g_memdup() has a flaw that will cause an overflow in over 32-bit numbers. // In the future, g_memdup2 will be needed, but it is not present // in glib 2.66. However, since SMIL should never be over 4294967295 // (max 32 bit unsigned value) chars long, I can just add a check. if (attach->length > G_MAXUINT) { g_critical ("Possible integer overflow! Aborting"); return FALSE; } attach->data = g_memdup (created_smil, attach->length); #endif msg->attachments = g_slist_append (msg->attachments, attach); msg->sr.content_type = g_strdup (CT_MULTIPART_RELATED); } else msg->sr.content_type = g_strdup (CT_MULTIPART_MIXED); if (send_message_get_attachments (attachments, msg, service) == FALSE) return FALSE; return TRUE; } static gchar * resolve_host_systemd (const char *host, struct mms_service *service) { gchar *host_ip = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) result = NULL; g_autoptr(GVariant) addresses = NULL; g_autoptr(GVariant) first_address = NULL; g_autoptr(GVariant) first_address_bytes = NULL; GVariantIter iter; guchar addr_byte; int first_address_family; unsigned char buf[sizeof(struct in6_addr)]; size_t i = 0; char str[INET6_ADDRSTRLEN] = { '\0' }; DBG ("%s", __func__); result = g_dbus_proxy_call_sync ( systemd_resolved_proxy, "ResolveHostname", g_variant_new ("(isit)", if_nametoindex (service->interface), host, AF_UNSPEC, 0), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error ); if (result == NULL) { g_warning ("Error while resolving hostname with systemd-resolved: %s\n", error->message); return NULL; } addresses = g_variant_get_child_value (result, 0); first_address = g_variant_get_child_value (addresses, 0); g_variant_get_child (first_address, 1, "i", &first_address_family); first_address_bytes = g_variant_get_child_value (first_address, 2); // iterate over GVariant byte array to convert to char array g_variant_iter_init (&iter, first_address_bytes); while (g_variant_iter_next (&iter, "y", &addr_byte) && (i < sizeof(struct in6_addr))) { buf[i] = addr_byte; i++; } inet_ntop (first_address_family, buf, str, INET6_ADDRSTRLEN); host_ip = g_strdup ((const gchar *) &str); DBG ("systemd-resolved host ip: %s", host_ip); return host_ip; } static void resolve_callback (void *arg, int status, int timeouts, struct hostent *host) { char **host_ip = (char **)arg; gchar ip[64]; if (!host || status != ARES_SUCCESS) { DBG ("Failed to resolve host: %s\n", ares_strerror (status)); return; } inet_ntop (host->h_addrtype, host->h_addr_list[0], ip, sizeof(ip)); *host_ip = g_strdup (ip); DBG ("Found IP for '%s': %s\n", host->h_name, *host_ip); } static void resolve_wait (ares_channel channel) { int nfds, count; fd_set readers, writers; struct timeval tv, *tvp; while (1) { FD_ZERO (&readers); FD_ZERO (&writers); nfds = ares_fds (channel, &readers, &writers); if (nfds == 0) break; tvp = ares_timeout (channel, NULL, &tv); count = select (nfds, &readers, &writers, NULL, tvp); if (count == -1) { g_critical ("Error waiting for c-ares read/write descriptors"); break; } ares_process (channel, &readers, &writers); } } static gchar * resolve_host_ares (const char *host, struct mms_service *service) { ares_channel chan; int ares_return; char *host_ip = NULL; g_autofree char *nsall_csv = NULL; DBG ("%s", __func__); ares_return = ares_init (&chan); if (ares_return != ARES_SUCCESS) { g_warning ("Ares init failed: %s\n", ares_strerror (ares_return)); goto ares_out; } /* * ares_set_local_dev () works without root * https://github.com/c-ares/c-ares/issues/405 */ DBG ("Binding resolver queries to interface %s", service->interface); ares_set_local_dev (chan, service->interface); if (service->resolvers_ipv6_csv && *service->resolvers_ipv6_csv && service->resolvers_ipv4_csv && *service->resolvers_ipv4_csv) nsall_csv = g_strjoin (",", service->resolvers_ipv6_csv, service->resolvers_ipv4_csv, NULL); else if (service->resolvers_ipv6_csv && *service->resolvers_ipv6_csv) nsall_csv = g_strdup (service->resolvers_ipv6_csv); else if (service->resolvers_ipv4_csv && *service->resolvers_ipv4_csv) nsall_csv = g_strdup (service->resolvers_ipv4_csv); else { g_warning ("No active DNS Servers\n"); goto ares_out; } DBG ("All Nameservers: %s", nsall_csv); ares_return = ares_set_servers_csv (chan, nsall_csv); if (ares_return != ARES_SUCCESS) g_warning ("Ares failed to set list of nameservers ('%s'), %s\n", nsall_csv, ares_strerror (ares_return)); ares_gethostbyname (chan, host, AF_UNSPEC, resolve_callback, (void *)&host_ip); resolve_wait (chan); if (host_ip == NULL) { g_warning ("Failed to resolve '%s'\n", host); goto ares_out; } ares_out: ares_destroy (chan); return host_ip; } static gchar * resolve_host (struct mms_service *service, const gchar *request_uri) { g_autoptr(GUri) uri = NULL; g_autoptr(GUri) resolved_uri = NULL; g_autofree gchar *host_ip = NULL; gchar *new_uri = NULL; const gchar *host = NULL; if (service->proxy_active == TRUE) { DBG ("There is an active proxy! Not attempting to resolve host"); new_uri = g_strdup (request_uri); DBG ("Using URI for request: %s", new_uri); return new_uri; } else DBG ("No active proxy"); // Parse host from uri uri = g_uri_parse (request_uri, SOUP_HTTP_URI_FLAGS, NULL); if (uri == NULL) { g_warning ("Unable to init new uri: %s\n", request_uri); return NULL; } host = g_uri_get_host (uri); if (systemd_resolved_proxy != NULL && !service->force_c_ares) host_ip = resolve_host_systemd (host, service); else host_ip = resolve_host_ares (host, service); if (host_ip != NULL) { resolved_uri = soup_uri_copy (uri, SOUP_URI_HOST, host_ip, SOUP_URI_NONE); new_uri = g_uri_to_string (resolved_uri); } DBG ("Using URI for request: %s", new_uri); return new_uri; } static struct mms_request * create_request (enum mms_request_type type, mms_request_result_cb_t result_cb, gchar *location, struct mms_service *service, struct mms_message *msg) { struct mms_request *request; request = g_try_new0 (struct mms_request, 1); if (request == NULL) return NULL; request->type = type; switch (request->type) { case MMS_REQUEST_TYPE_GET: request->data_path = g_strdup_printf ("%s%s", service->request_path, "receive.XXXXXX.mms"); break; case MMS_REQUEST_TYPE_POST: case MMS_REQUEST_TYPE_POST_TMP: request->data_path = g_strdup_printf ("%s%s", service->request_path, "send.XXXXXX.mms"); break; default: g_warning ("Not accounting for am MMS type"); break; } request->fd = g_mkstemp_full (request->data_path, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR); if (request->fd < 0) { mms_request_destroy (request); return NULL; } request->result_cb = result_cb; request->location = g_strdup (location); request->service = service; request->msg = msg; request->status = 0; request->attempt = 0; request->data_to_post_path = NULL; return request; } static gboolean bearer_setup_timeout (gpointer user_data) { struct mms_service *service = user_data; DBG ("Bearer Setup Timeout: Service %p", service); service->bearer_timeout = 0; service->bearer_setup = FALSE; return FALSE; } void activate_bearer (struct mms_service *service) { DBG ("service %p setup %d active %d", service, service->bearer_setup, service->bearer_active); /* Bearer is in the process of setting up already, do nothing */ if (service->bearer_setup == TRUE) return; /* Bearer is active already, process request queue */ if (service->bearer_active == TRUE) { process_request_queue (service); return; } /* Bearer is non-existant, do nothing */ if (service->bearer_handler == NULL) return; DBG ("service %p waiting for %d seconds", service, BEARER_SETUP_TIMEOUT); service->bearer_setup = TRUE; /* If bearer takes too long to set up, have a timeout to let mmsd-tng try again*/ service->bearer_timeout = g_timeout_add_seconds (BEARER_SETUP_TIMEOUT, bearer_setup_timeout, service); service->bearer_handler (TRUE, service->bearer_data); } static inline char * create_transaction_id (void) { return g_strdup_printf ("%08X%s", transaction_id_start++, "0123456789ABCDEF0123456789ABCDEF"); } static gboolean result_request_send_conf (struct mms_request *request) { struct mms_message *msg; struct mms_service *service = request->service; const char *uuid; GKeyFile *meta; void *pdu; size_t len; g_autofree char *path = NULL; if (request->msg == NULL) return FALSE; uuid = request->msg->uuid; path = g_strdup_printf ("%s/%s/%s", MMS_PATH, service->identity, uuid); if (request->status != 200) return FALSE; msg = g_try_new0 (struct mms_message, 1); if (msg == NULL) return FALSE; if (mmap_file (request->data_path, &pdu, &len) == FALSE) { mms_message_free (msg); return FALSE; } if (mms_message_decode (pdu, len, msg) == FALSE) { g_critical ("Failed to decode pdu %s", request->data_path); munmap (pdu, len); mms_message_free (msg); return FALSE; } if (msg->sc.rsp_status != MMS_MESSAGE_RSP_STATUS_OK) g_warning ("MMSC reported '%s'", message_rsp_status_to_string (msg->sc.rsp_status)); if (msg->sc.msgid == NULL) g_warning ("MMSC did not send a Message ID. Your MMS may not have gone through"); munmap (pdu, len); unlink (request->data_path); meta = mms_store_meta_open (service->identity, uuid); if (meta == NULL) { mms_message_free (msg); return FALSE; } if (msg->sc.msgid != NULL) g_key_file_set_string (meta, "info", "id", msg->sc.msgid); else g_key_file_set_string (meta, "info", "id", "invalid"); if ((msg->sc.rsp_status != MMS_MESSAGE_RSP_STATUS_OK) || (msg->sc.msgid == NULL)) { g_key_file_set_string (meta, "info", "state", "sending_failed"); request->msg->sr.status = MMS_MESSAGE_STATUS_SENDING_FAILED; emit_msg_status_changed (path, "sending_failed"); } else { g_key_file_set_string (meta, "info", "state", "sent"); request->msg->sr.status = MMS_MESSAGE_STATUS_SENT; emit_msg_status_changed (path, "sent"); } mms_message_free (msg); mms_store_meta_close (service->identity, uuid, meta, TRUE); return TRUE; } static void append_message_entry (char *path, const struct mms_service *service, struct mms_message *msg, GVariantBuilder *message_builder) { g_autoptr(GError) error = NULL; DBG ("Message Added %p", msg); append_message (msg->path, service, msg, message_builder); } static gboolean mms_attachment_is_smil (const struct mms_attachment *part) { if (g_str_match_string ("application/smil", part->content_type, TRUE)) return TRUE; return FALSE; } static void release_data (gpointer data, gpointer user_data) { struct mms_attachment *attach = data; if (mms_attachment_is_smil (attach)) g_free (attach->data); else munmap (attach->data, attach->length); } static void release_attachement_data (GSList *attach) { if (attach != NULL) g_slist_foreach (attach, release_data, NULL); } static void destroy_message (gpointer data) { struct mms_message *mms = data; mms_message_free (mms); } struct mms_service * mms_service_create (void) { struct mms_service *service; service = g_try_new0 (struct mms_service, 1); if (service == NULL) return NULL; service->refcount = 1; service->request_queue = g_queue_new (); if (service->request_queue == NULL) { g_free (service); return NULL; } service->web = NULL; service->current_request_msg = NULL; service->cancel_current_msg = NULL; /* * The key in the hash table is the MMS path, which is destroyed * in destroy_message(). Thus, no need to have a key_destroy_func */ service->messages = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, destroy_message); if (service->messages == NULL) { g_queue_free (service->request_queue); g_free (service); return NULL; } DBG ("Service create: %p", service); return service; } struct mms_service * mms_service_ref (struct mms_service *service) { if (service == NULL) return NULL; g_atomic_int_inc (&service->refcount); return service; } static void mms_message_dbus_unregister (unsigned int message_registration_id) { GDBusConnection *connection = mms_dbus_get_connection (); g_dbus_connection_unregister_object (connection, message_registration_id); } static void dbus_unregister_message (gpointer key, gpointer value, gpointer user_data) { //struct mms_service *service = user_data; struct mms_message *msg = value; mms_message_dbus_unregister (msg->message_registration_id); } static void destroy_message_table (struct mms_service *service) { if (service->messages == NULL) return; g_hash_table_foreach (service->messages, dbus_unregister_message, service); g_hash_table_destroy (service->messages); service->messages = NULL; } void mms_service_unref (struct mms_service *service) { struct mms_request *request; if (service == NULL) return; if (g_atomic_int_dec_and_test (&service->refcount) == FALSE) return; g_clear_object (&service->web); g_clear_pointer (&service->resolvers_ipv4_csv, g_free); g_clear_pointer (&service->resolvers_ipv6_csv, g_free); g_clear_object (&service->cancel_current_msg); while ((request = g_queue_pop_head (service->request_queue))) mms_request_destroy (request); g_queue_free (service->request_queue); destroy_message_table (service); g_free (service->mmsc); g_free (service->identity); g_free (service->path); g_free (service); } static void append_properties (GVariantBuilder *service_builder, struct mms_service *service) { g_variant_builder_add (service_builder, "o", service->path); g_variant_builder_open (service_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add_parsed (service_builder, "{'Identity', <%s>}", service->identity); g_variant_builder_close (service_builder); } static void emit_service_added (struct mms_service *service) { GDBusConnection *connection = mms_dbus_get_connection (); GVariantBuilder service_builder; GVariant *service_added; g_autoptr(GError) error = NULL; g_variant_builder_init (&service_builder, G_VARIANT_TYPE ("(oa{sv})")); DBG ("Service Added %p", service); append_properties (&service_builder, service); service_added = g_variant_builder_end (&service_builder); g_dbus_connection_emit_signal (connection, NULL, MMS_PATH, MMS_MANAGER_INTERFACE, "ServiceAdded", service_added, &error); if (error != NULL) { g_warning ("Error in Proxy call: %s\n", error->message); error = NULL; } } static void emit_service_removed (struct mms_service *service) { GDBusConnection *connection = mms_dbus_get_connection (); GVariant *servicepathvariant; g_autoptr(GError) error = NULL; servicepathvariant = g_variant_new ("(o)", service->path); g_dbus_connection_emit_signal (connection, NULL, MMS_PATH, MMS_MANAGER_INTERFACE, "ServiceRemoved", servicepathvariant, &error); if (error != NULL) g_warning ("Error in Proxy call: %s\n", error->message); } static gboolean load_message_from_store (const char *service_id, const char *uuid, struct mms_message *msg) { GKeyFile *meta; g_autofree char *state = NULL; gboolean read_status; g_autofree char *data_path = NULL; g_autofree char *datestr = NULL; gboolean success = FALSE; gboolean tainted = FALSE; void *pdu; size_t len; struct tm tm; meta = mms_store_meta_open (service_id, uuid); if (meta == NULL) return FALSE; state = g_key_file_get_string (meta, "info", "state", NULL); if (state == NULL) goto out; read_status = g_key_file_get_boolean (meta, "info", "read", NULL); datestr = g_key_file_get_string (meta, "info", "date", NULL); if (datestr != NULL) strptime (datestr, "%Y-%m-%dT%H:%M:%S%z", &tm); else { time_t date; const char *datestrconst = NULL; DBG ("src/service.c:load_message_from_store() There is no date stamp!"); DBG ("src/service.c:load_message_from_store() Setting time to now."); time (&date); datestrconst = time_to_str (&date); strptime (datestrconst, "%Y-%m-%dT%H:%M:%S%z", &tm); g_free (datestr); datestr = g_strdup (datestrconst); DBG ("src/service.c:load_message_from_store() Time is %s.", datestr); g_key_file_set_string (meta, "info", "date", datestr); } data_path = mms_store_get_path (service_id, uuid); if (data_path == NULL) goto out; if (mmap_file (data_path, &pdu, &len) == FALSE) goto out; if (mms_message_decode (pdu, len, msg) == FALSE) { g_critical ("Failed to decode %s", data_path); munmap (pdu, len); tainted = TRUE; goto out; } munmap (pdu, len); msg->uuid = g_strdup (uuid); if (strcmp (state, "received") == 0 && msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF) { msg->rc.datestamp = g_strdup (datestr); msg->rc.date = mktime (&tm); if (read_status == TRUE) msg->rc.status = MMS_MESSAGE_STATUS_READ; else msg->rc.status = MMS_MESSAGE_STATUS_RECEIVED; } else if (strcmp (state, "downloaded") == 0 && msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF) { msg->rc.status = MMS_MESSAGE_STATUS_DOWNLOADED; if (msg->transaction_id == NULL) { g_critical ("Downloaded Message has no transaction ID!"); msg->transaction_id = g_strdup (""); } } else if (strcmp (state, "sent") == 0 && msg->type == MMS_MESSAGE_TYPE_SEND_REQ) { msg->sr.datestamp = g_strdup (datestr); msg->sr.date = mktime (&tm); msg->sr.status = MMS_MESSAGE_STATUS_SENT; msg->sr.dr = g_key_file_get_boolean (meta, "info", "delivery_report", NULL); if (msg->sr.dr) msg->sr.delivery_recipients = g_key_file_get_string (meta, "info", "delivery_recipients", NULL); else msg->sr.delivery_recipients = NULL; } else if (strcmp (state, "draft") == 0 && msg->type == MMS_MESSAGE_TYPE_SEND_REQ) { msg->sr.datestamp = g_strdup (datestr); msg->sr.date = mktime (&tm); msg->sr.status = MMS_MESSAGE_STATUS_DRAFT; msg->sr.dr = g_key_file_get_boolean (meta, "info", "delivery_report", NULL); if (msg->sr.dr) msg->sr.delivery_recipients = g_key_file_get_string (meta, "info", "delivery_recipients", NULL); else msg->sr.delivery_recipients = NULL; } else if (strcmp (state, "sending_failed") == 0 && msg->type == MMS_MESSAGE_TYPE_SEND_REQ) { msg->sr.datestamp = g_strdup (datestr); msg->sr.date = mktime (&tm); msg->sr.status = MMS_MESSAGE_STATUS_SENDING_FAILED; msg->sr.dr = g_key_file_get_boolean (meta, "info", "delivery_report", NULL); if (msg->sr.dr) msg->sr.delivery_recipients = g_key_file_get_string (meta, "info", "delivery_recipients", NULL); else msg->sr.delivery_recipients = NULL; } else if (strcmp (state, "delivered") == 0 && msg->type == MMS_MESSAGE_TYPE_SEND_REQ) { msg->sr.datestamp = g_strdup (datestr); msg->sr.date = mktime (&tm); msg->sr.status = MMS_MESSAGE_STATUS_DELIVERED; msg->sr.dr = g_key_file_get_boolean (meta, "info", "delivery_report", NULL); if (msg->sr.dr) msg->sr.delivery_recipients = g_key_file_get_string (meta, "info", "delivery_recipients", NULL); else msg->sr.delivery_recipients = NULL; } else if (msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND) { g_autofree char *expirystr = NULL; const char *expirystr_new = NULL; expirystr = g_key_file_get_string (meta, "info", "expiration", NULL); if (expirystr) { struct tm tm2; strptime (expirystr, "%Y-%m-%dT%H:%M:%S%z", &tm2); msg->ni.expiry = mktime (&tm2); } else { expirystr_new = time_to_str (&msg->ni.expiry); g_key_file_set_string (meta, "info", "expiration", expirystr_new); } } else if (msg->type != MMS_MESSAGE_TYPE_DELIVERY_IND) goto out; success = TRUE; out: mms_store_meta_close (service_id, uuid, meta, TRUE); if (tainted == TRUE) mms_store_remove (service_id, uuid); return success; } static struct mms_request * build_notify_resp_ind (struct mms_service *service, enum mms_message_notify_status status, struct mms_message *rc_msg) { struct mms_message *ni_msg; struct mms_request *notify_request; gboolean result; ni_msg = g_try_new0 (struct mms_message, 1); if (ni_msg == NULL) return NULL; ni_msg->type = MMS_MESSAGE_TYPE_NOTIFYRESP_IND; ni_msg->version = MMS_MESSAGE_VERSION_1_3; ni_msg->transaction_id = g_strdup (rc_msg->transaction_id); ni_msg->nri.notify_status = status; notify_request = create_request (MMS_REQUEST_TYPE_POST_TMP, result_request_notify_resp, NULL, service, rc_msg); if (notify_request == NULL) { mms_message_free (ni_msg); return NULL; } if (status == MMS_MESSAGE_NOTIFY_STATUS_UNRECOGNISED) notify_request->msg = NULL; /* * We are building an MMS awknowledging that we just downloaded the one * from transation ID. */ result = mms_message_encode (ni_msg, notify_request->fd); close (notify_request->fd); notify_request->fd = -1; mms_message_free (ni_msg); if (result == FALSE) { unlink (notify_request->data_path); mms_request_destroy (notify_request); return NULL; } return notify_request; } static gboolean process_delivery_ind (struct mms_service *service, struct mms_message *msg) { g_autofree char *formatted_to = NULL; const char *delivery_status; GHashTableIter table_iter; gpointer key, value; gboolean update_successful = FALSE; DBG ("At process_delivery_ind"); mms_address_to_string (msg->di.to); formatted_to = phone_utils_format_number_e164 (msg->di.to, service->country_code, TRUE); g_message ("Msg ID: %s\n", msg->di.msgid); //g_message("To: %s\n", formatted_to); switch (msg->di.dr_status) { case MMS_MESSAGE_DELIVERY_STATUS_EXPIRED: delivery_status = "expired"; break; case MMS_MESSAGE_DELIVERY_STATUS_RETRIEVED: delivery_status = "retrieved"; break; case MMS_MESSAGE_DELIVERY_STATUS_REJECTED: delivery_status = "rejected"; break; case MMS_MESSAGE_DELIVERY_STATUS_DEFERRED: delivery_status = "deferred"; break; case MMS_MESSAGE_DELIVERY_STATUS_UNRECOGNISED: delivery_status = "unrecognized"; break; case MMS_MESSAGE_DELIVERY_STATUS_INDETERMINATE: delivery_status = "indeterminate"; break; case MMS_MESSAGE_DELIVERY_STATUS_FORWARDED: delivery_status = "forwarded"; break; case MMS_MESSAGE_DELIVERY_STATUS_UNREACHABLE: delivery_status = "unreachable"; break; default: delivery_status = "error"; break; } g_message ("Delivery Report status: %d, %s \n", msg->di.dr_status, delivery_status); g_hash_table_iter_init (&table_iter, service->messages); while (g_hash_table_iter_next (&table_iter, &key, &value)) { GKeyFile *meta; struct mms_message *delivery_msg = value; g_autofree char *msgid = NULL; meta = mms_store_meta_open (service->identity, delivery_msg->uuid); msgid = g_key_file_get_string (meta, "info", "id", NULL); if (msgid != NULL) //DBG("On message %s: %s", delivery_msg->uuid, msgid); if (strcmp (msg->di.msgid, msgid) == 0) { g_autofree char *path = g_strdup_printf ("%s/%s/%s", MMS_PATH, service->identity, delivery_msg->uuid); g_autofree char *delivery_update = g_strdup_printf ("delivery_update,%s=%s", formatted_to, delivery_status); int delivery_number, total_delivery_number; g_key_file_set_string (meta, "delivery_status", formatted_to, delivery_status); delivery_number = g_key_file_get_integer (meta, "delivery_status", "delivery_number_complete", NULL); delivery_number = delivery_number + 1; g_key_file_set_integer (meta, "delivery_status", "delivery_number_complete", delivery_number); total_delivery_number = g_key_file_get_integer (meta, "info", "delivery_recipients_number", NULL); //DBG("delivery_update: %s", delivery_update); emit_msg_status_changed (path, delivery_update); if (delivery_number == total_delivery_number) { DBG ("All Recipients delivered (or had error)"); delivery_msg->sr.status = MMS_MESSAGE_STATUS_DELIVERED; g_key_file_set_string (meta, "info", "state", "delivered"); emit_msg_status_changed (path, "delivered"); } mms_store_meta_close (service->identity, delivery_msg->uuid, meta, TRUE); update_successful = TRUE; break; } mms_store_meta_close (service->identity, delivery_msg->uuid, meta, FALSE); } return update_successful; } static gboolean check_sr_message_expiration (struct mms_service *service, struct mms_message *msg) { time_t now; if (msg->type != MMS_MESSAGE_TYPE_SEND_REQ) { g_critical ("This is not a send request! This function won't do anything"); return TRUE; } if (msg->sr.status == MMS_MESSAGE_STATUS_SENDING_FAILED) { DBG ("This message is indicating that sending it failed already"); return FALSE; } now = time (NULL); /* * If we have been trying to send for more than three hours (10800 Seconds), * just consider the sending failed. */ if (now >= msg->sr.date + 10800) { GKeyFile *meta; const char *uuid; g_autofree char *path = NULL; uuid = msg->uuid; path = g_strdup_printf ("%s/%s/%s", MMS_PATH, service->identity, uuid); DBG ("Attempt to send message has happened for too long!"); meta = mms_store_meta_open (service->identity, uuid); if (meta == NULL) return FALSE; g_key_file_set_string (meta, "info", "state", "sending_failed"); msg->sr.status = MMS_MESSAGE_STATUS_SENDING_FAILED; emit_msg_status_changed (path, "sending_failed"); mms_store_meta_close (service->identity, uuid, meta, TRUE); return FALSE; } return TRUE; } static gboolean check_ni_message_expiration (struct mms_service *service, struct mms_message *msg) { time_t now; if (msg->type != MMS_MESSAGE_TYPE_NOTIFICATION_IND) { g_critical ("This is not a notification! This function won't do anything"); return TRUE; } now = time (NULL); if (now >= msg->ni.expiry) { DBG ("Message is expired!"); mms_message_register (service, msg); emit_message_added (service, msg); service->notification_ind = service->notification_ind - 1; DBG ("Number of notifications: %i", service->notification_ind); return FALSE; } return TRUE; } static void process_message_on_start (struct mms_service *service, const char *uuid) { struct mms_message *msg; struct mms_request *request; const char *service_id = service->identity; msg = g_try_new0 (struct mms_message, 1); if (msg == NULL) return; if (load_message_from_store (service_id, uuid, msg) == FALSE) { DBG ("Failed to load_message_from_store() from MMS with uuid %s", uuid); mms_message_free (msg); return; } if (msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND) { request = create_request (MMS_REQUEST_TYPE_GET, result_request_retrieve_conf, msg->ni.location, service, msg); if (request == NULL) { mms_message_free (msg); return; } service->notification_ind = service->notification_ind + 1; DBG ("Number of notifications: %i", service->notification_ind); } else if (msg->type == MMS_MESSAGE_TYPE_SEND_REQ) { if (msg->sr.status == MMS_MESSAGE_STATUS_DRAFT) { request = create_request (MMS_REQUEST_TYPE_POST, result_request_send_conf, NULL, service, NULL); if (request == NULL) goto register_sr; close (request->fd); request->fd = -1; /* * The data_path is for encoding the message to send, * but since we already built it, just delete it. */ unlink (request->data_path); g_free (request->data_path); request->data_path = mms_store_get_path (service_id, uuid); request->msg = msg; } else request = NULL; register_sr: mms_message_register (service, msg); } else if (msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF) { if (msg->rc.status == MMS_MESSAGE_STATUS_DOWNLOADED) { request = build_notify_resp_ind (service, MMS_MESSAGE_NOTIFY_STATUS_RETRIEVED, msg); if (request == NULL) mms_message_free (msg); } else { request = NULL; mms_message_register (service, msg); } } else if (msg->type == MMS_MESSAGE_TYPE_DELIVERY_IND) { if (process_delivery_ind (service, msg) == FALSE) DBG ("There was an issue finding the message to go with the delivery Notification. Was it deleted?"); DBG ("Deleting Delivery Notification"); mms_store_remove (service->identity, msg->uuid); mms_message_free (msg); request = NULL; } else request = NULL; if (request != NULL) { /* We are rebuilding the queue, so just do a FIFO queue */ g_queue_push_tail (service->request_queue, request); activate_bearer (service); } } static void remove_stale_requests (struct mms_service *service) { GDir *dir; const char *file; g_autofree char *service_path = NULL; dir = g_dir_open (service->request_path, 0, NULL); if (dir == NULL) return; while ((file = g_dir_read_name (dir)) != NULL) { const size_t suffix_len = 4; g_autofree char *filepath = g_strdup_printf ("%s%s", service->request_path, file); if (g_str_has_suffix (file, ".mms") == FALSE) continue; if (strlen (file) - suffix_len == 0) continue; unlink (filepath); } g_dir_close (dir); } static void load_messages (struct mms_service *service) { GDir *dir; const char *file; const char *homedir; g_autofree char *service_path = NULL; homedir = g_get_home_dir (); if (homedir == NULL) return; service_path = g_strdup_printf ("%s/.mms/%s/", homedir, service->identity); dir = g_dir_open (service_path, 0, NULL); if (dir == NULL) return; while ((file = g_dir_read_name (dir)) != NULL) { const size_t suffix_len = 7; g_autofree char *uuid = NULL; if (g_str_has_suffix (file, ".status") == FALSE) continue; if (strlen (file) - suffix_len == 0) continue; uuid = g_strndup (file, strlen (file) - suffix_len); process_message_on_start (service, uuid); } g_dir_close (dir); } GKeyFile * mms_service_get_keyfile (struct mms_service *service) { return service->settings; } int mms_service_register (struct mms_service *service) { GDBusConnection *connection = mms_dbus_get_connection (); GDBusNodeInfo *introspection_data = mms_dbus_get_introspection_data (); g_autoptr(GError) error = NULL; DBG ("Service Register: %p", service); if (service == NULL) return -EINVAL; if (service->identity == NULL) return -EINVAL; if (service->path != NULL) return -EBUSY; service->path = g_strdup_printf ("%s/%s", MMS_PATH, service->identity); if (service->path == NULL) return -ENOMEM; service_registration_id = g_dbus_connection_register_object (connection, service->path, introspection_data->interfaces[0], &interface_vtable_service, service, // user_data NULL, // user_data_free_func &error); // GError** if (error) g_critical ("Error Registering Service: %s", error->message); g_assert (service_registration_id > 0); service_list = g_list_append (service_list, service); emit_service_added (service); mms_load_settings (service); service->request_path = g_strdup_printf ("%s/mmstng/%s/", g_get_user_cache_dir (), service->identity); if (g_mkdir_with_parents (service->request_path, 0777) == -1) g_critical ("Could not make mms request path!"); remove_stale_requests (service); load_messages (service); ares_library_init (ARES_LIB_INIT_ALL); return 0; } int mms_service_unregister (struct mms_service *service) { GDBusConnection *connection = mms_dbus_get_connection (); DBG ("Service Unregister: %p", service); if (service == NULL) return -EINVAL; if (service->path == NULL) return -EINVAL; if (service->messages != NULL) destroy_message_table (service); if (service->settings != NULL) { g_key_file_set_boolean (service->settings, SETTINGS_GROUP, "UseDeliveryReports", service->use_delivery_reports); mms_settings_close (service->identity, SETTINGS_STORE, service->settings, TRUE); service->settings = NULL; } //Disconnect the service dbus interface g_dbus_connection_unregister_object (connection, service_registration_id); service_list = g_list_remove (service_list, service); emit_service_removed (service); g_clear_pointer (&service->country_code, g_free); g_clear_pointer (&service->path, g_free); g_clear_pointer (&service->interface, g_free); g_clear_pointer (&service->own_number, g_free); g_clear_pointer (&service->apn, g_free); g_clear_pointer (&service->request_path, g_free); ares_library_cleanup (); return 0; } static const char * time_to_str (const time_t *t) { static char buf[128]; struct tm tm; strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime_r (t, &tm)); buf[127] = '\0'; DBG ("Time %ld, Human Format %s", *t, buf); return buf; } static void append_attachment_properties (struct mms_attachment *part, GVariantBuilder *attachment_builder, const char *path) { g_autofree char *temp_content_id = NULL; g_autofree char *temp_content_type = NULL; if (strlen (part->content_id) == 0) { // Some MMSes do not encode a filename for the attachment. If this happens, // the value of part->content_id will be empty. Rather than make // this a chat applicaton's problem, I am adding a random filename here. DBG ("Content ID is empty, manually adding random id..."); temp_content_id = g_uuid_string_random (); DBG ("fixed content-id: %s\n", temp_content_id); } else temp_content_id = g_strdup (part->content_id); // send_message_get_attachments () adds stuff to content type. // I am filtering them out if (strstr (part->content_type, "Content-Type: ")) { g_auto(GStrv) tokens = NULL; DBG ("Need to fix content type %s", part->content_type); tokens = g_strsplit (part->content_type, "\"", -1); temp_content_type = g_strdup (tokens[1]); DBG ("fixed content-type: %s\n", temp_content_type); } else temp_content_type = g_strdup (part->content_type); g_variant_builder_add (attachment_builder, "(ssstt)", temp_content_id, temp_content_type, path, part->offset, part->length); } static void append_smil (GVariantBuilder *message_builder, const char *path, const struct mms_attachment *part) { const char *to_codeset = "utf-8"; char *from_codeset; void *data; size_t len; g_autofree char *smil = NULL; if (mmap_file (path, &data, &len) == FALSE) return; from_codeset = mms_content_type_get_param_value (part->content_type, "charset"); if (from_codeset != NULL) { smil = g_convert ((const char *) data + part->offset, part->length, to_codeset, from_codeset, NULL, NULL, NULL); g_free (from_codeset); } else smil = g_convert ((const char *) data + part->offset, part->length, to_codeset, "us-ascii", NULL, NULL, NULL); munmap (data, len); if (smil == NULL) { g_critical ("Failed to convert smil attachment\n"); return; } g_variant_builder_add_parsed (message_builder, "{'Smil', <%s>}", smil); } static inline void check_null_content_id (struct mms_attachment *attachment) { if (attachment->content_id == NULL) attachment->content_id = g_strdup (""); } static gboolean check_empty_subject (const char *subject) { for (guint i = 0; i < G_N_ELEMENTS (empty_subjects); i++) if (g_strcmp0 (empty_subjects[i], subject) == 0) return TRUE; return FALSE; } static void append_msg_subject (GVariantBuilder *message_builder, const char *subject) { const char *subj = check_empty_subject (subject) ? "" : subject; g_variant_builder_add_parsed (message_builder, "{'Subject', <%s>}", subj); } static void append_msg_attachments (GVariantBuilder *message_builder, const char *path, struct mms_message *msg) { GSList *part; struct mms_attachment *smil; GVariantBuilder attachment_builder; GVariant *attachments; g_variant_builder_init (&attachment_builder, G_VARIANT_TYPE_ARRAY); smil = NULL; for (part = msg->attachments; part != NULL; part = g_slist_next (part)) { check_null_content_id (part->data); if (mms_attachment_is_smil (part->data)) smil = part->data; else append_attachment_properties (part->data, &attachment_builder, path); } attachments = g_variant_builder_end (&attachment_builder); g_variant_builder_add (message_builder, "{sv}", "Attachments", attachments); if (smil == NULL) { DBG ("No Smil!"); return; } DBG ("Attaching Smil!"); switch (msg->type) { case MMS_MESSAGE_TYPE_SEND_REQ: append_smil (message_builder, path, smil); return; case MMS_MESSAGE_TYPE_SEND_CONF: return; case MMS_MESSAGE_TYPE_NOTIFICATION_IND: return; case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: return; case MMS_MESSAGE_TYPE_RETRIEVE_CONF: append_smil (message_builder, path, smil); break; case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: return; case MMS_MESSAGE_TYPE_DELIVERY_IND: return; default: g_warning ("Did not account for a message type"); return; } } static void append_msg_recipients (GVariantBuilder *message_builder, struct mms_message *msg, const struct mms_service *service) { g_auto(GStrv) tokens = NULL; unsigned int i; const char *rcpt; GVariantBuilder recipient_builder; GVariant *recipients; g_variant_builder_init (&recipient_builder, G_VARIANT_TYPE_ARRAY); switch (msg->type) { case MMS_MESSAGE_TYPE_SEND_REQ: tokens = g_strsplit (msg->sr.to, ",", -1); break; case MMS_MESSAGE_TYPE_SEND_CONF: return; case MMS_MESSAGE_TYPE_NOTIFICATION_IND: return; case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: return; case MMS_MESSAGE_TYPE_RETRIEVE_CONF: tokens = g_strsplit (msg->rc.to, ",", -1); break; case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: return; case MMS_MESSAGE_TYPE_DELIVERY_IND: return; default: g_warning ("Did not account for a message type"); return; } for (i = 0; tokens[i] != NULL; i++) { g_autofree char *formatted_rcpt = NULL; rcpt = mms_address_to_string (tokens[i]); //DBG("rcpt=%s", rcpt); formatted_rcpt = phone_utils_format_number_e164 (rcpt, service->country_code, TRUE); //DBG("Formatted rcpt=%s", formatted_rcpt); g_variant_builder_add (&recipient_builder, "s", formatted_rcpt); } recipients = g_variant_builder_end (&recipient_builder); g_variant_builder_add (message_builder, "{sv}", "Recipients", recipients); } static void append_ni_msg_properties (GVariantBuilder *message_builder, struct mms_message *msg, const struct mms_service *service) { const char *expire_time = time_to_str (&msg->ni.expiry); const char *status = "expired"; const char *from_prefix; g_autofree char *from = NULL; time_t now; if (msg->type != MMS_MESSAGE_TYPE_NOTIFICATION_IND) { g_critical ("This is not a notification! This function won't do anything"); return; } now = time (NULL); if (now < msg->ni.expiry) { DBG ("This notification is not expired!"); return; } g_variant_builder_add_parsed (message_builder, "{'Status', <%s>}", status); g_variant_builder_add_parsed (message_builder, "{'Expire', <%s>}", expire_time); append_msg_subject (message_builder, msg->ni.subject); from = g_strdup (msg->ni.from); if (from != NULL) { g_autofree char *formatted_from = NULL; from_prefix = mms_address_to_string (from); formatted_from = phone_utils_format_number_e164 (from_prefix, service->country_code, TRUE); g_variant_builder_add_parsed (message_builder, "{'Sender', <%s>}", formatted_from); } if (service->own_number != NULL) g_variant_builder_add_parsed (message_builder, "{'Modem Number', <%s>}", service->own_number); else g_variant_builder_add_parsed (message_builder, "{'Modem Number', <''>}"); } static void append_rc_msg_properties (GVariantBuilder *message_builder, struct mms_message *msg, const struct mms_service *service) { const char *date = time_to_str (&msg->rc.date); const char *status = "received"; const char *from_prefix; g_autofree char *from = NULL; g_variant_builder_add_parsed (message_builder, "{'Status', <%s>}", status); g_variant_builder_add_parsed (message_builder, "{'Date', <%s>}", date); append_msg_subject (message_builder, msg->rc.subject); from = g_strdup (msg->rc.from); if (from != NULL) { g_autofree char *formatted_from = NULL; from_prefix = mms_address_to_string (from); formatted_from = phone_utils_format_number_e164 (from_prefix, service->country_code, TRUE); g_variant_builder_add_parsed (message_builder, "{'Sender', <%s>}", formatted_from); } if (msg->rc.to != NULL) append_msg_recipients (message_builder, msg, service); if (service->own_number != NULL) g_variant_builder_add_parsed (message_builder, "{'Modem Number', <%s>}", service->own_number); else g_variant_builder_add_parsed (message_builder, "{'Modem Number', <''>}"); } static void append_sr_msg_properties (GVariantBuilder *message_builder, struct mms_message *msg, const struct mms_service *service) { const char *status = mms_message_status_get_string (msg->sr.status); g_variant_builder_add_parsed (message_builder, "{'Status', <%s>}", status); g_variant_builder_add_parsed (message_builder, "{'Date', <%s>}", msg->sr.datestamp); append_msg_subject (message_builder, msg->sr.subject); g_variant_builder_add_parsed (message_builder, "{'Delivery Report', <%b>}", msg->sr.dr); if (msg->sr.dr) { char **tos; GString *to_concat = g_string_new (NULL); GKeyFile *meta = mms_store_meta_open (service->identity, msg->uuid); tos = g_strsplit (msg->sr.delivery_recipients, ",", 0); for (int i = 0; tos[i] != NULL; i++) { g_autofree char *delivery_status = NULL; delivery_status = g_key_file_get_string (meta, "delivery_status", tos[i], NULL); to_concat = g_string_append (to_concat, tos[i]); to_concat = g_string_append (to_concat, "="); to_concat = g_string_append (to_concat, delivery_status); to_concat = g_string_append (to_concat, ","); } to_concat = g_string_truncate (to_concat, (strlen (to_concat->str) - 1)); g_variant_builder_add_parsed (message_builder, "{'Delivery Status', <%s>}", to_concat->str); g_string_free (to_concat, TRUE); g_strfreev (tos); mms_store_meta_close (service->identity, msg->uuid, meta, FALSE); } if (service->own_number != NULL) g_variant_builder_add_parsed (message_builder, "{'Modem Number', <%s>}", service->own_number); else g_variant_builder_add_parsed (message_builder, "{'Modem Number', <''>}"); if (msg->sr.to != NULL) append_msg_recipients (message_builder, msg, service); } static void append_message (const char *path, const struct mms_service *service, struct mms_message *msg, GVariantBuilder *message_builder) { g_variant_builder_add (message_builder, "o", msg->path); g_variant_builder_open (message_builder, G_VARIANT_TYPE ("a{sv}")); switch (msg->type) { case MMS_MESSAGE_TYPE_SEND_REQ: append_sr_msg_properties (message_builder, msg, service); break; case MMS_MESSAGE_TYPE_SEND_CONF: break; case MMS_MESSAGE_TYPE_NOTIFICATION_IND: append_ni_msg_properties (message_builder, msg, service); break; case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: break; case MMS_MESSAGE_TYPE_RETRIEVE_CONF: append_rc_msg_properties (message_builder, msg, service); break; case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: break; case MMS_MESSAGE_TYPE_DELIVERY_IND: break; default: g_warning ("Did not account for a message type"); break; } if (msg->attachments != NULL) { char *pdu_path = mms_store_get_path (service->identity, msg->uuid); DBG ("appending pdu path %s", pdu_path); append_msg_attachments (message_builder, pdu_path, msg); g_free (pdu_path); } g_variant_builder_close (message_builder); } static void emit_message_added (const struct mms_service *service, struct mms_message *msg) { GDBusConnection *connection = mms_dbus_get_connection (); GVariantBuilder message_builder; GVariant *message; g_autoptr(GError) error = NULL; g_variant_builder_init (&message_builder, G_VARIANT_TYPE ("(oa{sv})")); append_message (msg->path, service, msg, &message_builder); message = g_variant_builder_end (&message_builder); g_dbus_connection_emit_signal (connection, NULL, service->path, MMS_SERVICE_INTERFACE, "MessageAdded", message, &error); if (error != NULL) g_warning ("Error in Proxy call: %s\n", error->message); } int mms_message_register (struct mms_service *service, struct mms_message *msg) { GDBusConnection *connection = mms_dbus_get_connection (); GDBusNodeInfo *introspection_data = mms_dbus_get_introspection_data (); g_autoptr(GError) error = NULL; //This builds the path for the message, do not disturb! msg->path = g_strdup_printf ("%s/%s", service->path, msg->uuid); if (msg->path == NULL) return -ENOMEM; msg->message_registration_id = g_dbus_connection_register_object (connection, msg->path, introspection_data->interfaces[2], &interface_vtable_message, service, // user_data NULL, // user_data_free_func &error); // GError** if (error) g_critical ("Error Registering Message %s: %s", msg->path, error->message); g_assert (msg->message_registration_id > 0); g_hash_table_replace (service->messages, msg->path, msg); DBG ("message registered %s", msg->path); return 0; } static void emit_message_removed (const char *svc_path, const char *msg_path) { GDBusConnection *connection = mms_dbus_get_connection (); GVariant *msgpathvariant; g_autoptr(GError) error = NULL; msgpathvariant = g_variant_new ("(o)", msg_path); g_dbus_connection_emit_signal (connection, NULL, svc_path, MMS_SERVICE_INTERFACE, "MessageRemoved", msgpathvariant, &error); if (error != NULL) g_warning ("Error in Proxy call: %s\n", error->message); } int mms_message_unregister (const struct mms_service *service, const char *msg_path, guint message_registration_id) { emit_message_removed (service->path, msg_path); mms_message_dbus_unregister (message_registration_id); DBG ("message unregistered %s", msg_path); g_hash_table_remove (service->messages, msg_path); return 0; } int mms_service_set_identity (struct mms_service *service, const char *identity) { DBG ("service %p identity %s", service, identity); if (service == NULL) return -EINVAL; if (service->path != NULL) return -EBUSY; g_free (service->identity); service->identity = g_strdup (identity); return 0; } int mms_service_set_country_code (struct mms_service *service, const char *imsi) { const char *country_code; if (imsi == NULL) { g_warning ("mms_service_set_country_code(): IMSI is NULL!"); return FALSE; } country_code = get_country_iso_for_mcc (imsi); if (country_code == NULL) { g_warning ("mms_service_set_country_code(): Country Code is NULL!"); return FALSE; } g_free (service->country_code); service->country_code = g_strdup (country_code); DBG ("Service Country Code set to %s", service->country_code); return TRUE; } int mms_service_set_own_number (struct mms_service *service, const char *own_number) { g_autofree char *new_number = NULL; if (own_number == NULL) { g_warning ("mms_service_set_own_number(): Number is NULL!"); return FALSE; } if (service->country_code == NULL) { g_warning ("Country Code is NULL!"); return FALSE; } new_number = phone_utils_format_number_e164 (own_number, service->country_code, FALSE); if (new_number) { g_clear_pointer (&service->own_number, g_free); service->own_number = g_strdup (new_number); DBG ("Service own number set"); } else { g_warning ("Error setting own number!"); return FALSE; } return TRUE; } const char * mms_service_get_own_number (struct mms_service *service) { return service->own_number; } void mms_service_set_apn (struct mms_service *service, const char *apn) { g_clear_pointer (&service->apn, g_free); if (apn == NULL) return; service->apn = g_strdup (apn); DBG ("Service APN Set to %s", service->apn); return; } int mms_service_set_mmsc (struct mms_service *service, const gchar *mmsc) { DBG ("service %p mmsc %s", service, mmsc); if (service == NULL) return -EINVAL; g_free (service->mmsc); service->mmsc = g_strdup (mmsc); return 0; } int mms_service_set_resolvers (struct mms_service *service, const gchar *ipv4_csv, const gchar *ipv6_csv) { DBG ("service %p resolvers: ipv4: %s, ipv6: %s", service, ipv4_csv, ipv6_csv); if (service == NULL) return -EINVAL; g_clear_pointer (&service->resolvers_ipv4_csv, g_free); if (ipv4_csv && *ipv4_csv) service->resolvers_ipv4_csv = g_strdup (ipv4_csv); g_clear_pointer (&service->resolvers_ipv6_csv, g_free); if (ipv6_csv && *ipv6_csv) service->resolvers_ipv6_csv = g_strdup (ipv6_csv); return 0; } int mms_service_set_bearer_handler (struct mms_service *service, mms_service_bearer_handler_func_t handler, void *user_data) { DBG ("service %p handler %p", service, handler); if (service == NULL) return -EINVAL; service->bearer_handler = handler; service->bearer_data = user_data; return 0; } static inline gboolean bearer_is_active (struct mms_service *service) { if (service->bearer_setup == TRUE) return FALSE; if (service->bearer_handler == NULL) return FALSE; return service->bearer_active; } static void deactivate_bearer (struct mms_service *service) { DBG ("Deactivate Bearer: Service %p", service); if (bearer_is_active (service) == FALSE) return; service->bearer_setup = TRUE; service->bearer_timeout = g_timeout_add_seconds (BEARER_SETUP_TIMEOUT, bearer_setup_timeout, service); service->bearer_handler (FALSE, service->bearer_data); } static gboolean bearer_idle_timeout (gpointer user_data) { struct mms_service *service = user_data; DBG ("Bearer Idle Timeout: Service %p", service); service->bearer_timeout = 0; deactivate_bearer (service); return FALSE; } static gboolean result_request_notify_resp (struct mms_request *request) { struct mms_message *msg; GKeyFile *meta; const char *datestr = NULL; unlink (request->data_path); if (request->status != 200) { g_warning ("POST m.notify.resp.ind failed with status %d", request->status); return FALSE; } /* * Now that the MMS Server awknowledged our response, we can safely * delete the original request. */ if (request->type == MMS_REQUEST_TYPE_POST_TMP) { DBG ("Destroying post data from Notify Resp"); unlink (request->data_to_post_path); } if (request->msg == NULL) { g_critical ("POST m.notify.resp.ind provided no message to register"); return FALSE; } msg = mms_request_steal_message (request); if (mms_message_register (request->service, msg) != 0) { mms_message_free (msg); return FALSE; } emit_message_added (request->service, msg); meta = mms_store_meta_open (request->service->identity, msg->uuid); if (meta == NULL) return FALSE; datestr = time_to_str (&msg->rc.date); g_free (msg->rc.datestamp); msg->rc.datestamp = g_strdup (datestr); g_key_file_set_string (meta, "info", "date", msg->rc.datestamp); g_key_file_set_string (meta, "info", "state", "received"); mms_store_meta_close (request->service->identity, msg->uuid, meta, TRUE); return TRUE; } static gboolean result_request_retrieve_conf (struct mms_request *request) { struct mms_message *msg; struct mms_service *service = request->service; g_autofree char *uuid = NULL; GKeyFile *meta; void *pdu; size_t len; struct mms_request *notify_request; gboolean decode_success; if (request->status != 200) return FALSE; if (mmap_file (request->data_path, &pdu, &len) == FALSE) return FALSE; uuid = mms_store_file (service->identity, request->data_path); if (uuid == NULL) goto exit; msg = g_try_new0 (struct mms_message, 1); if (msg == NULL) goto exit; decode_success = mms_message_decode (pdu, len, msg); msg->transaction_id = g_strdup (request->msg->transaction_id); if (decode_success == TRUE) { msg->uuid = g_strdup (uuid); meta = mms_store_meta_open (service->identity, uuid); if (meta == NULL) goto error; g_key_file_set_boolean (meta, "info", "read", FALSE); g_key_file_set_string (meta, "info", "state", "downloaded"); mms_store_meta_close (service->identity, uuid, meta, TRUE); notify_request = build_notify_resp_ind (service, MMS_MESSAGE_NOTIFY_STATUS_RETRIEVED, msg); } else { g_critical ("Failed to decode %s", request->data_path); notify_request = NULL; } /* Remove notify.ind pdu */ mms_store_remove (service->identity, request->msg->uuid); mms_message_free (request->msg); service->notification_ind = service->notification_ind - 1; DBG ("Number of notifications: %i", service->notification_ind); if (notify_request == NULL) goto error; /* Prioritize the notify_request finishing */ g_queue_push_head (service->request_queue, notify_request); activate_bearer (service); if (decode_success == TRUE) goto exit; error: mms_store_remove (service->identity, uuid); mms_message_free (msg); exit: munmap (pdu, len); return TRUE; } static gboolean mms_requeue_request (struct mms_request *request) { request->attempt += 1; if (request->type == MMS_REQUEST_TYPE_GET) { if (request->fd == -1) { DBG ("Reopening file...."); request->fd = open (request->data_path, O_WRONLY | O_TRUNC, S_IWUSR | S_IRUSR); } if (request->fd < 0) return FALSE; } else if (request->type == MMS_REQUEST_TYPE_POST || request->type == MMS_REQUEST_TYPE_POST_TMP) { if (request->fd != -1) close (request->fd); request->fd = -1; /* * If request->data_to_post_path is not NULL, then we need to delete * the temp file in request->data_path and put request->data_to_post_path * back into request->data_path */ DBG ("Data Path is %s", request->data_path); DBG ("Data to POST Path is %s", request->data_to_post_path); if (request->data_to_post_path) { DBG ("Putting Data to POST Path to Data path"); unlink (request->data_path); g_free (request->data_path); request->data_path = g_strdup (request->data_to_post_path); g_clear_pointer (&request->data_to_post_path, g_free); } } /* Do NOT prioritize a requeued message */ g_queue_push_tail (request->service->request_queue, request); return TRUE; } static gboolean service_activate_bearer (gpointer user_data) { struct mms_service *service = user_data; DBG ("Retrying Modem Bearer"); activate_bearer (service); return FALSE; } static void emit_message_tx_rx_error (struct mms_request *request, const char *host, enum mms_tx_rx_error error_type) { GDBusConnection *connection = mms_dbus_get_connection (); g_autoptr(GError) error = NULL; GVariant *message_error; if (request->msg->type == MMS_MESSAGE_TYPE_SEND_REQ) { GVariantBuilder error_builder; GVariant *incomplete_error; g_variant_builder_init (&error_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add_parsed (&error_builder, "{'ErrorType', <%u>}", error_type); g_variant_builder_add_parsed (&error_builder, "{'Host', <%s>}", host); g_variant_builder_add_parsed (&error_builder, "{'MessagePath', <%s>}", request->msg->path); incomplete_error = g_variant_builder_end (&error_builder); message_error = g_variant_new ("(*)", incomplete_error); g_dbus_connection_emit_signal (connection, NULL, request->service->path, MMS_SERVICE_INTERFACE, "MessageSendError", message_error, &error); } else if (request->msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND || request->msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF) { GVariantBuilder error_builder; GVariant *incomplete_error; const char *from_prefix = NULL; g_autofree char *formatted_from = NULL; if (request->msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND && request->msg->ni.from != NULL) { from_prefix = mms_address_to_string (request->msg->ni.from); formatted_from = phone_utils_format_number_e164 (from_prefix, request->service->country_code, TRUE); } else if (request->msg->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF && request->msg->rc.from != NULL) { from_prefix = mms_address_to_string (request->msg->rc.from); formatted_from = phone_utils_format_number_e164 (from_prefix, request->service->country_code, TRUE); } else { g_critical ("Did not find who this is from, this should not happen!"); formatted_from = g_strdup (""); } g_variant_builder_init (&error_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add_parsed (&error_builder, "{'ErrorType', <%u>}", error_type); g_variant_builder_add_parsed (&error_builder, "{'Host', <%s>}", host); g_variant_builder_add_parsed (&error_builder, "{'From', <%s>}", formatted_from); incomplete_error = g_variant_builder_end (&error_builder); message_error = g_variant_new ("(*)", incomplete_error); g_dbus_connection_emit_signal (connection, NULL, request->service->path, MMS_SERVICE_INTERFACE, "MessageReceiveError", message_error, &error); } else g_critical ("Confused....This was not a send or receive request."); if (error != NULL) g_warning ("Error in Proxy call: %s\n", error->message); } static gboolean service_retry_process_request_queue (gpointer user_data) { struct mms_service *service = user_data; DBG ("Retrying Queue"); process_request_queue (service); return FALSE; } static void on_message_done (SoupSession *session, GAsyncResult *result, gpointer user_data) { struct mms_request *request = user_data; struct mms_service *service = request->service; gsize written = 0; gsize body_length = 0; SoupMessage *msg; GBytes *body = NULL; g_autoptr(GError) error = NULL; msg = soup_session_get_async_result_message (session, result); body = soup_session_send_and_read_finish (session, result, &error); body_length = g_bytes_get_size (body); g_clear_object (&service->cancel_current_msg); request->status = soup_message_get_status (msg); if (error) { g_warning ("Error in sending/receiving: %s", error->message); request->status = 007; } if (!SOUP_STATUS_IS_SUCCESSFUL (request->status)) g_warning ("Fail to get data (http status = %03u)", request->status); DBG ("status: %03u", request->status); DBG ("data size = %zd", body_length); written = write (request->fd, g_bytes_get_data (body, &body_length), body_length); if (written != g_bytes_get_size (body)) g_warning ("only %zd/%zd bytes written\n", written, body_length); g_bytes_unref (body); close (request->fd); request->fd = -1; DBG ("request->result_cb=%p vs. retrieve_conf=%p/send_conf=%p/notify_resp=%p", request->result_cb, result_request_retrieve_conf, result_request_send_conf, result_request_notify_resp); service->current_request_msg = NULL; if (request->result_cb == NULL || request->result_cb (request) == TRUE) mms_request_destroy (request); else { g_warning ("Fail to get data (http status = %03u)", request->status); if (mms_requeue_request (request) == TRUE) { DBG ("On attempt %d", request->attempt); if (request->attempt >= MAX_ATTEMPTS) { const char *max_attempt_host = NULL; g_autoptr(GUri) g_uri = NULL; DBG ("Had Max attempts"); request->attempt = 0; if (request->type == MMS_REQUEST_TYPE_GET || request->type == MMS_REQUEST_TYPE_POST_TMP) g_uri = g_uri_parse (request->location, SOUP_HTTP_URI_FLAGS, NULL); else if (request->type == MMS_REQUEST_TYPE_POST) g_uri = g_uri_parse (request->service->mmsc, SOUP_HTTP_URI_FLAGS, NULL); if (g_uri != NULL) max_attempt_host = g_uri_get_host (g_uri); else max_attempt_host = ""; emit_message_tx_rx_error (request, max_attempt_host, MMS_TX_RX_ERROR_HTTP); g_timeout_add_seconds (120, service_activate_bearer, service); deactivate_bearer (service); return; } else { DBG ("Requeued Message"); g_timeout_add_seconds (5, service_retry_process_request_queue, service); return; } } else { unlink (request->data_path); mms_request_destroy (request); } } process_request_queue (service); } static void soupmessage_network_event_cb (SoupMessage *msg, GSocketClientEvent event, GSocketConnection *connection, gpointer user_data) { struct mms_request *request = user_data; GSocket *msg_socket; int socket_fd; /* https://mail.gnome.org/archives/libsoup-list/2012-December/msg00011.html */ /* Bind the SoupMessage to service->interface */ if (event == G_SOCKET_CLIENT_CONNECTING) { int return_code, interface_length; msg_socket = g_socket_connection_get_socket (connection); socket_fd = g_socket_get_fd (msg_socket); interface_length = strlen (request->service->interface); DBG ("Socket %d Binding to %s length %d", socket_fd, request->service->interface, interface_length); return_code = setsockopt (socket_fd, SOL_SOCKET, SO_BINDTODEVICE, request->service->interface, interface_length); if (return_code == 0) DBG ("Socket is bound to %s", request->service->interface); else DBG ("Socket could not be bound! Error: %d", return_code); g_signal_handler_disconnect (msg, request->soupmessage_network_event_signal_id); } } static int check_verizon_apn (const char *apn) { g_autofree char *apn_to_test = NULL; apn_to_test = g_utf8_strdown (apn, -1); if ((g_strcmp0 (apn_to_test, "vzwapp") == 0) || (g_strcmp0 (apn_to_test, "vzwinternet") == 0) || (g_strcmp0 (apn_to_test, "vzwims") == 0)) return TRUE; else return FALSE; return FALSE; } /* * TODO: There looks to be other headers that can be needed for MMS: * https://cs.android.com/search?q=httpParams&ss=android%2Fplatform%2Fsuperproject:packages%2Fapps%2FMessaging%2Fres%2F * I see: * X-MDN * x-carrier-magic * X-CS3G-MDN * x-up-calling-line-id * */ static void message_add_headers (struct mms_request *request, SoupMessage **msg) { SoupMessageHeaders *message_headers = NULL; if (check_verizon_apn (request->service->apn)) { char *number = NULL; if (request->service->own_number == NULL) { g_critical ("Cannot add number to headers!"); return; } number = request->service->own_number + 1; message_headers = soup_message_get_request_headers ((*msg)); soup_message_headers_replace (message_headers, "X-VzW-MDN", number); } } /* Verizon send either a wap push with an incomplete url and we need to concat * the transaction id, or send 2 wap push with a full url. * We need testers on US Cellular and Xfinity mobile, as it *may* be needed too for some of them. * We may need to check the MNC/MCC instead of the apn as us cellular apn is very generic. */ static void append_transaction_id (struct mms_request *request) { char *new_location = NULL; if (request->location == NULL) return; if (check_verizon_apn (request->service->apn) && (g_str_has_suffix (request->location, "message-id="))) { new_location = g_strconcat (request->location, request->msg->transaction_id, NULL); DBG ("Changing Location from %s to %s", request->location, new_location); g_clear_pointer (&request->location, g_free); request->location = new_location; } } static void create_new_web_message (struct mms_request *request, const char *type, const char *location, SoupMessage **msg) { SoupMessageHeaders *message_headers = NULL; g_autofree char *host_header = NULL; g_autofree char *uri = NULL; g_autoptr(GUri) guri = NULL; const char *host = NULL; int port; uri = resolve_host (request->service, location); if (uri == NULL) { g_warning ("Failed to resolve DNS"); if (mms_requeue_request (request) == TRUE) { DBG ("On attempt %d", request->attempt); if (request->attempt >= MAX_ATTEMPTS) { const char *max_attempt_host = NULL; g_autoptr(GUri) g_uri = NULL; DBG ("Had Max attempts"); request->attempt = 0; if (request->type == MMS_REQUEST_TYPE_GET || request->type == MMS_REQUEST_TYPE_POST_TMP) g_uri = g_uri_parse (request->location, SOUP_HTTP_URI_FLAGS, NULL); else if (request->type == MMS_REQUEST_TYPE_POST) g_uri = g_uri_parse (request->service->mmsc, SOUP_HTTP_URI_FLAGS, NULL); if (g_uri != NULL) max_attempt_host = g_uri_get_host (g_uri); else max_attempt_host = ""; emit_message_tx_rx_error (request, max_attempt_host, MMS_TX_RX_ERROR_DNS); g_timeout_add_seconds (120, service_activate_bearer, request->service); deactivate_bearer (request->service); return; } else { DBG ("Requeued Message"); return; } } else { unlink (request->data_path); mms_request_destroy (request); return; } } if (*msg != NULL) g_object_unref (*msg); if (g_strcmp0 (type, "GET") == 0) { SoupMessageHeaders *message_headers_get = NULL; *msg = soup_message_new ("GET", uri); if (*msg == NULL) { g_critical ("unable to create new libsoup GET message\n"); return; } message_headers_get = soup_message_get_request_headers ((*msg)); soup_message_headers_replace (message_headers_get, "Accept", "*/*"); } else if (g_strcmp0 (type, "POST") == 0) { *msg = soup_message_new ("POST", uri); if (*msg == NULL) { g_critical ("unable to create new libsoup POST message\n"); return; } } else { g_critical ("Unknown message type: %s", type); return; } /* Make sure that the mesage connects on new socket */ soup_message_set_flags (*msg, SOUP_MESSAGE_NEW_CONNECTION); /* * Some carriers depend on the Host: header being set to the * location. resolve_host returns an IP */ guri = g_uri_parse (location, SOUP_HTTP_URI_FLAGS, NULL); port = g_uri_get_port (guri); host = g_uri_get_host (guri); DBG ("host: %s, port: %d", host, port); message_headers = soup_message_get_request_headers ((*msg)); if (port == 80 || port == -1) soup_message_headers_replace (message_headers, "Host", host); else { host_header = g_strdup_printf ("%s:%d", host, port); soup_message_headers_replace (message_headers, "Host", host_header); } if (request->service->proxy_active == TRUE) soup_message_headers_replace (message_headers, "Proxy-Connection", "Keep-Alive"); /* * Fake the user agent to look like Android * https://cs.android.com/android/platform/superproject/+/master:packages/apps/Messaging/src/android/support/v7/mms/DefaultUserAgentInfoLoader.java;drc=f38a1bdc3c5b47024fe4c605e6a487e5353dfedf;l=29 */ soup_message_headers_replace (message_headers, "User-Agent", "Android MmsLib/1.0"); message_add_headers (request, msg); /* Monitor "network-event" for G_SOCKET_CLIENT_CONNECTING */ request->soupmessage_network_event_signal_id = g_signal_connect ( *msg, "network-event", G_CALLBACK (soupmessage_network_event_cb), request); } static SoupMessage * process_request (struct mms_request *request) { struct mms_service *service = request->service; SoupMessage *msg = NULL; if (request->data_path == NULL) return NULL; append_transaction_id (request); switch (request->type) { case MMS_REQUEST_TYPE_GET: create_new_web_message (request, "GET", request->location, &msg); if (msg == NULL) { g_critical ("Unable to create GET message"); return NULL; } service->cancel_current_msg = g_cancellable_new (); soup_session_send_and_read_async (request->service->web, msg, 0, service->cancel_current_msg, (GAsyncReadyCallback)on_message_done, request); DBG ("GET from <%s>", request->location); return msg; case MMS_REQUEST_TYPE_POST: case MMS_REQUEST_TYPE_POST_TMP: /* Collect contents of file to POST */ gsize length; g_autofree gchar *contents = NULL; GBytes *request_body = NULL; SoupMessageHeaders *message_headers = NULL; create_new_web_message (request, "POST", service->mmsc, &msg); if (msg == NULL) { g_critical ("Unable to create POST message"); return NULL; } g_file_get_contents (request->data_path, &contents, &length, NULL); if (contents == NULL) { g_critical ("Unable to read contents of file: %s\n", request->data_path); return NULL; } request_body = g_bytes_new (contents, length); request->data_to_post_path = g_strdup (request->data_path); if (request->fd == -1) { /* Prepare fd for response reception */ g_free (request->data_path); request->data_path = g_strdup_printf ("%spost-rsp.XXXXXX.mms", service->request_path); request->fd = g_mkstemp_full (request->data_path, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR); } message_headers = soup_message_get_request_headers (msg); soup_message_headers_replace (message_headers, "Content-Type", DEFAULT_CONTENT_TYPE); soup_message_set_request_body_from_bytes (msg, NULL, request_body); g_bytes_unref (request_body); service->cancel_current_msg = g_cancellable_new (); soup_session_send_and_read_async (request->service->web, msg, 0, service->cancel_current_msg, (GAsyncReadyCallback)on_message_done, request); DBG ("POST %lu bytes to %s", length, service->mmsc); DBG ("Sending <%s>", request->data_to_post_path); return msg; default: g_critical ("You should not have gotten here"); } g_critical ("Cannot process request (request type: %d)", request->type); unlink (request->data_path); mms_request_destroy (request); return NULL; } static void process_request_queue (struct mms_service *service) { struct mms_request *request; DBG ("Process Request Queue: Service %p", service); /* If there is an idle bearer timeout, remove it */ if (service->bearer_timeout > 0) { g_source_remove (service->bearer_timeout); service->bearer_timeout = 0; } /* Service is already processing a request, do nothing */ if (service->current_request_msg) return; /* Bearer is not active, do nothing */ if (bearer_is_active (service) == FALSE) return; request = g_queue_pop_head (service->request_queue); while (request != NULL && (request->msg == NULL || (request->msg->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND && check_ni_message_expiration (service, request->msg) == FALSE) || (request->msg->type == MMS_MESSAGE_TYPE_SEND_REQ && check_sr_message_expiration (service, request->msg) == FALSE))) { if (request->msg == NULL) DBG ("Request message was NULL!"); mms_request_destroy (request); request = g_queue_pop_head (service->request_queue); } /* We have no requests in the queue */ if (request == NULL) { /* We have no requests in the queue, start an idle timeout */ service->bearer_timeout = g_timeout_add_seconds (BEARER_IDLE_TIMEOUT, bearer_idle_timeout, service); return; } DBG ("location %s", request->location); service->current_request_msg = process_request (request); /* We have an active request now */ if (service->current_request_msg) return; /* There was an error processing the request */ g_timeout_add_seconds (5, service_retry_process_request_queue, service); } static void dump_delivery_ind (struct mms_message *msg) { char buf[128]; strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (&msg->di.date)); buf[127] = '\0'; g_message ("MMS version: %u.%u\n", (msg->version & 0x70) >> 4, msg->version & 0x0f); g_message ("Msg ID: %s\n", msg->di.msgid); mms_address_to_string (msg->di.to); //g_message("To: %s\n", msg->di.to); g_message ("Date: %s\n", buf); g_message ("Delivery Report status: %d\n", msg->di.dr_status); } static void dump_notification_ind (struct mms_message *msg) { char buf[128]; strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (&msg->ni.expiry)); buf[127] = '\0'; g_message ("MMS transaction id: %s\n", msg->transaction_id); g_message ("MMS version: %u.%u\n", (msg->version & 0x70) >> 4, msg->version & 0x0f); //g_message("From: %s\n", msg->ni.from); g_message ("Subject: %s\n", msg->ni.subject); g_message ("Class: %s\n", msg->ni.cls); g_message ("Size: %d\n", msg->ni.size); g_message ("Expiry: %s\n", buf); g_message ("Location: %s\n", msg->ni.location); } static gboolean mms_push_notify (const unsigned char *pdu, unsigned int len, unsigned int *offset) { unsigned int headerslen; unsigned int param_len; const void *ct; const void *aid; struct wsp_header_iter iter; unsigned int nread; unsigned int consumed; unsigned int i; GString *hex; DBG ("pdu %p len %d", pdu, len); hex = g_string_sized_new (len * 2); for (i = 0; i < len; i++) g_string_append_printf (hex, "%02X", pdu[i]); DBG ("%s", hex->str); g_string_free (hex, TRUE); /* PUSH pdu ? */ if (pdu[1] != 0x06) return FALSE; /* Consume TID and Type */ nread = 2; if (wsp_decode_uintvar (pdu + nread, len, &headerslen, &consumed) == FALSE) return FALSE; /* Consume uintvar bytes */ nread += consumed; /* Try to decode content-type */ if (wsp_decode_content_type (pdu + nread, headerslen, &ct, &consumed, ¶m_len) == FALSE) return FALSE; if (ct == NULL) return FALSE; /* Consume Content Type bytes, including parameters */ consumed += param_len; nread += consumed; /* Parse header to decode application_id */ wsp_header_iter_init (&iter, pdu + nread, headerslen - consumed, 0); aid = NULL; while (wsp_header_iter_next (&iter)) { const unsigned char *wk; /* Skip application headers */ if (wsp_header_iter_get_hdr_type (&iter) != WSP_HEADER_TYPE_WELL_KNOWN) continue; wk = wsp_header_iter_get_hdr (&iter); if ((wk[0] & 0x7f) != WSP_HEADER_TOKEN_APP_ID) continue; if (wsp_decode_application_id (&iter, &aid) == FALSE) return FALSE; } if (wsp_header_iter_at_end (&iter) == FALSE) return FALSE; nread += headerslen - consumed; g_message ("Body Length: %d\n", len - nread); DBG ("Content Type: %s", (char *) ct); if (g_str_equal (ct, MMS_CONTENT_TYPE) == TRUE) { if (offset != NULL) *offset = nread; return TRUE; } return FALSE; } void mms_service_push_notify (struct mms_service *service, const unsigned char *data, int len) { struct mms_request *request; struct mms_message *msg; unsigned int nread; g_autofree char *uuid = NULL; const char *expirystr; GKeyFile *meta; DBG ("Processing push notify"); msg = g_try_new0 (struct mms_message, 1); if (msg == NULL) { g_critical ("Failed to allocate message"); return; } if (mms_push_notify (data, len, &nread) == FALSE) goto out; uuid = mms_store (service->identity, data + nread, len - nread); if (uuid == NULL) goto out; if (mms_message_decode (data + nread, len - nread, msg) == FALSE) goto error; if (msg->type == MMS_MESSAGE_TYPE_DELIVERY_IND) { msg->uuid = g_strdup (uuid); dump_delivery_ind (msg); meta = mms_store_meta_open (service->identity, uuid); if (meta == NULL) goto error; g_key_file_set_string (meta, "info", "state", "notification"); mms_store_meta_close (service->identity, uuid, meta, TRUE); if (process_delivery_ind (service, msg) == FALSE) DBG ("There was an issue finding the message to go with the delivery Notification. Was it deleted?"); mms_store_remove (service->identity, msg->uuid); mms_message_free (msg); return; } if (msg->type != MMS_MESSAGE_TYPE_NOTIFICATION_IND) goto error; msg->uuid = g_strdup (uuid); dump_notification_ind (msg); meta = mms_store_meta_open (service->identity, uuid); if (meta == NULL) goto error; g_key_file_set_boolean (meta, "info", "read", FALSE); g_key_file_set_string (meta, "info", "state", "notification"); expirystr = time_to_str (&msg->ni.expiry); g_key_file_set_string (meta, "info", "expiration", expirystr); mms_store_meta_close (service->identity, uuid, meta, TRUE); request = create_request (MMS_REQUEST_TYPE_GET, result_request_retrieve_conf, msg->ni.location, service, msg); if (request == NULL) goto out; /* Prioritize a new push notify */ g_queue_push_head (service->request_queue, request); service->notification_ind = service->notification_ind + 1; DBG ("Number of notifications: %i", service->notification_ind); activate_bearer (service); return; error: mms_store_remove (service->identity, uuid); out: mms_message_free (msg); g_critical ("Failed to handle incoming notification"); } void debug_print (const char *s, void *data) { printf ("%s\n", s); } void mms_service_bearer_notify (struct mms_service *service, mms_bool_t active, const char *interface, const char *proxy) { int ifindex; g_autoptr(GError) error = NULL; DBG ("service=%p active=%d iface=%s proxy=%s", service, active, interface, proxy); if (service == NULL) return; if (service->bearer_timeout > 0) { g_source_remove (service->bearer_timeout); service->bearer_timeout = 0; } service->bearer_setup = FALSE; service->bearer_active = active; service->proxy_active = FALSE; if (active == FALSE) goto interface_down; DBG ("interface %s proxy %s", interface, proxy); if (interface == NULL) goto interface_down; ifindex = if_nametoindex (interface); if (ifindex == 0) goto interface_down; if (service->interface != NULL) g_free (service->interface); service->interface = g_strdup (interface); if (service->current_request_msg) { DBG ("Cancelling active message request!"); g_cancellable_cancel (service->cancel_current_msg); g_clear_object (&service->cancel_current_msg); g_clear_object (&service->current_request_msg); } g_clear_object (&service->web); service->web = soup_session_new (); if (service->web == NULL) return; if (global_debug) { SoupLogger *logger; logger = soup_logger_new (SOUP_LOGGER_LOG_BODY); soup_session_add_feature (service->web, SOUP_SESSION_FEATURE (logger)); g_object_unref (logger); } if (proxy && *proxy) { g_autofree char *uri = NULL; g_autofree char *new_uri = NULL; g_autofree char *uri_to_use_temp = NULL; g_autofree char *uri_to_use = NULL; gchar **tokens; g_autoptr(GUri) proxy_uri = NULL; GProxyResolver *default_proxy = NULL; int port; if (g_str_has_prefix (proxy, "http://")) { uri = g_strdup (proxy); tokens = g_strsplit (proxy + 7, ":", 2); } else { uri = g_strdup_printf ("http://%s", proxy); tokens = g_strsplit (proxy, ":", 2); } if (!g_hostname_is_ip_address (tokens[0])) { DBG ("Hostname is not an IP address!"); new_uri = resolve_host (service, uri); DBG ("Proxy URL resolved: %s", new_uri); } else new_uri = g_strdup (uri); g_strfreev (tokens); if (new_uri == NULL) { g_critical ("Failed to resolve proxy"); goto interface_down; } proxy_uri = g_uri_parse (new_uri, SOUP_HTTP_URI_FLAGS, NULL); if (!proxy_uri) { g_critical ("unable to set proxy: %s", new_uri); goto interface_down; } uri_to_use_temp = g_uri_to_string (proxy_uri); if (g_str_has_suffix (uri_to_use_temp, "/")) uri_to_use = g_strndup (uri_to_use_temp, strlen (uri_to_use_temp) - 1); else uri_to_use = g_strdup (uri_to_use_temp); port = g_uri_get_port (proxy_uri); /* g_simple_proxy_resolver_new() seems to require the port enumerated */ if (port == 80 || port == -1) { g_clear_pointer (&uri_to_use_temp, g_free); uri_to_use_temp = uri_to_use; uri_to_use = g_strdup_printf ("%s:80", uri_to_use_temp); } DBG ("Proxy URL: %s", uri_to_use); default_proxy = g_simple_proxy_resolver_new (uri_to_use, NULL); soup_session_set_proxy_resolver (service->web, default_proxy); g_object_unref (default_proxy); service->proxy_active = TRUE; } else soup_session_set_proxy_resolver (service->web, NULL); DBG ("Proxy is set to %d", service->proxy_active); process_request_queue (service); return; interface_down: if (service->current_request_msg) { DBG ("Cancelling active message request!"); g_cancellable_cancel (service->cancel_current_msg); g_clear_object (&service->cancel_current_msg); g_clear_object (&service->current_request_msg); } g_clear_object (&service->web); service->bearer_active = FALSE; } static void systemd_resolved_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { g_autoptr(GError) error = NULL; DBG ("systemd-resolved is on the system bus, creating proxy"); systemd_resolved_proxy = g_dbus_proxy_new_sync ( connection, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, RESOLVED_SERVICE, RESOLVED_PATH, RESOLVED_MANAGER_INTERFACE, NULL, &error ); if (systemd_resolved_proxy == NULL) g_warning ("Error while acquiring DBus proxy for systemd-resolved: %s\n", error->message); } static void systemd_resolved_vanished (GDBusConnection *connection, const gchar *name, gpointer user_data) { DBG ("Lost systemd-resolved on the system bus"); g_clear_object (&systemd_resolved_proxy); } int __mms_service_init (gboolean enable_debug) { GDBusConnection *connection = mms_dbus_get_connection (); GDBusNodeInfo *introspection_data = mms_dbus_get_introspection_data (); g_autoptr(GError) error = NULL; global_debug = enable_debug; DBG ("Starting Up MMSD Service Manager"); manager_registration_id = g_dbus_connection_register_object (connection, MMS_PATH, introspection_data->interfaces[1], &interface_vtable_manager, NULL, // user_data NULL, // user_data_free_func &error); // GError** systemd_resolved_watcher_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, RESOLVED_SERVICE, G_BUS_NAME_WATCHER_FLAGS_NONE, systemd_resolved_appeared, systemd_resolved_vanished, NULL, NULL); if (error) g_critical ("Error Registering Manager: %s", error->message); return 0; } void __mms_service_cleanup (void) { GDBusConnection *connection = mms_dbus_get_connection (); g_bus_unwatch_name (systemd_resolved_watcher_id); systemd_resolved_vanished (connection, NULL, NULL); DBG ("Cleaning Up MMSD Service Manager"); //Disconnect the manager dbus interface g_dbus_connection_unregister_object (connection, manager_registration_id); } mmsd-2.6.0/src/service.h000066400000000000000000000073741456374454100151070ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * 2021, Clayton Craft * 2020, Anteater * * 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. * * 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 "types.h" #define DEFAULT_MAX_ATTACHMENT_TOTAL_SIZE 1100000 struct mms_service; struct mms_message; typedef void (*mms_service_bearer_handler_func_t) (mms_bool_t active, void *user_data); struct mms_service *mms_service_create (void); struct mms_service *mms_service_ref (struct mms_service *service); void mms_service_unref (struct mms_service *service); int mms_service_register (struct mms_service *service); int mms_service_unregister (struct mms_service *service); int mms_service_set_identity (struct mms_service *service, const char *identity); int mms_service_set_mmsc (struct mms_service *service, const gchar *mmsc); int mms_service_set_resolvers (struct mms_service *service, const gchar *ipv4_csv, const gchar *ipv6_csv); int mms_service_set_bearer_handler (struct mms_service *service, mms_service_bearer_handler_func_t handler, void *user_data); void mms_service_push_notify (struct mms_service *service, const unsigned char *data, int len); void mms_service_bearer_notify (struct mms_service *service, mms_bool_t active, const char *interface, const char *proxy); int mms_message_register (struct mms_service *service, struct mms_message *msg); int mms_message_unregister (const struct mms_service *service, const char *msg_path, guint message_registration_id); void activate_bearer (struct mms_service *service); GKeyFile *mms_service_get_keyfile (struct mms_service *service); int mms_service_set_country_code (struct mms_service *service, const char *imsi); gchar *mms_message_format_number_e164 (const char *number, const char *country_code, gboolean return_original_number); int mms_service_set_own_number (struct mms_service *service, const char *own_number); const char *mms_service_get_own_number (struct mms_service *service); void mms_service_set_apn (struct mms_service *service, const char *apn); void service_set_max_attach_size (struct mms_service *service, int max_size); int service_get_max_attach_size (struct mms_service *service); mmsd-2.6.0/src/store.c000066400000000000000000000207531456374454100145720ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include #include #include #include #include #include #include #include "mms.h" #ifdef TEMP_FAILURE_RETRY #define TFR TEMP_FAILURE_RETRY #else #define TFR #endif static int create_dirs (const char *filename, const mode_t mode) { struct stat st; char *dir; const char *prev, *next; int err; if (filename[0] != '/') return -1; err = stat (filename, &st); if (!err && S_ISREG (st.st_mode)) return 0; dir = g_try_malloc (strlen (filename) + 1); if (dir == NULL) return -1; strcpy (dir, "/"); for (prev = filename; (next = strchr (prev + 1, '/')); prev = next) { /* Skip consecutive '/' characters */ if (next - prev == 1) continue; strncat (dir, prev + 1, next - prev); if (mkdir (dir, mode) == -1 && errno != EEXIST) { g_free (dir); return -1; } } g_free (dir); return 0; } static char * generate_uuid_objpath (void) { char *uuid, *reformatted; uuid = g_uuid_string_random (); reformatted = g_strdelimit (g_strdup (uuid), "-", '1'); g_free (uuid); return reformatted; } char * mms_store_get_path (const char *service_id, const char *uuid) { const char *homedir; homedir = g_get_home_dir (); if (homedir == NULL) return NULL; return g_strdup_printf ("%s/.mms/%s/%s", homedir, service_id, uuid); } static char * generate_pdu_pathname (const char *service_id, const char *uuid) { char *pathname; pathname = mms_store_get_path (service_id, uuid); if (pathname == NULL) return NULL; if (create_dirs (pathname, S_IRUSR | S_IWUSR | S_IXUSR) != 0) { g_critical ("Failed to create path %s", pathname); g_free (pathname); return NULL; } return pathname; } /* * Write a buffer to a file in a transactionally safe form * * Given a buffer, write it to a file named after * @filename. However, to make sure the file contents are * consistent (ie: a crash right after opening or during write() * doesn't leave a file half baked), the contents are written to a * file with a temporary name and when closed, it is renamed to the * specified name (@filename). */ static ssize_t write_file (const unsigned char *buffer, size_t len, const char *filename) { char *tmp_file; ssize_t written; int fd; tmp_file = g_strdup_printf ("%s.XXXXXX.tmp", filename); written = -1; fd = TFR (g_mkstemp_full (tmp_file, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)); if (fd < 0) goto error_mkstemp_full; written = TFR (write (fd, buffer, len)); TFR (fdatasync (fd)); TFR (close (fd)); if (written != (ssize_t) len) { written = -1; goto error_write; } /* * Now that the file contents are written, rename to the real * file name; this way we are uniquely sure that the whole * thing is there. */ unlink (filename); /* conserve @written's value from 'write' */ if (link (tmp_file, filename) == -1) written = -1; error_write: unlink (tmp_file); error_mkstemp_full: g_free (tmp_file); return written; } char * mms_store (const char *service_id, const unsigned char *pdu, unsigned int len) { char *pathname; char *uuid; uuid = generate_uuid_objpath (); if (uuid == NULL) return NULL; pathname = generate_pdu_pathname (service_id, uuid); if (pathname == NULL) return NULL; if (write_file (pdu, len, pathname) < 0) { g_critical ("Failed to write to %s", pathname); uuid = NULL; } DBG ("pathname %s", pathname); g_free (pathname); return uuid; } char * mms_store_file (const char *service_id, const char *path) { char *pathname; char *uuid; int fd; struct stat st; unsigned char *pdu; fd = open (path, O_RDONLY); if (fd < 0) { g_critical ("Failed to open %s\n", path); return NULL; } if (fstat (fd, &st) < 0) { g_critical ("Failed to fstat %s\n", path); close (fd); return NULL; } pdu = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (pdu == NULL || pdu == MAP_FAILED) { g_critical ("Failed to mmap %s\n", path); close (fd); return NULL; } uuid = generate_uuid_objpath (); munmap (pdu, st.st_size); close (fd); if (uuid == NULL) return NULL; pathname = generate_pdu_pathname (service_id, uuid); if (pathname == NULL) return NULL; if (g_rename (path, pathname) < 0) { g_critical ("Failed to rename %s to %s\n", path, pathname); uuid = NULL; } DBG ("pathname %s", pathname); g_free (pathname); return uuid; } void mms_store_remove (const char *service_id, const char *uuid) { char *pdu_path; char *meta_path; pdu_path = mms_store_get_path (service_id, uuid); if (pdu_path == NULL) return; unlink (pdu_path); meta_path = g_strdup_printf ("%s%s", pdu_path, MMS_META_UUID_SUFFIX); g_free (pdu_path); unlink (meta_path); g_free (meta_path); } GKeyFile * mms_store_meta_open (const char *service_id, const char *uuid) { GKeyFile *keyfile; g_autofree char *pdu_path = NULL; g_autofree char *meta_path = NULL; pdu_path = generate_pdu_pathname (service_id, uuid); if (pdu_path == NULL) return NULL; meta_path = g_strdup_printf ("%s%s", pdu_path, MMS_META_UUID_SUFFIX); keyfile = g_key_file_new (); g_key_file_load_from_file (keyfile, meta_path, 0, NULL); return keyfile; } static void meta_store_sync (const char *service_id, const char *uuid, GKeyFile *keyfile) { char *data; gsize length = 0; char *pdu_path; char *meta_path; pdu_path = mms_store_get_path (service_id, uuid); if (pdu_path == NULL) return; meta_path = g_strdup_printf ("%s%s", pdu_path, MMS_META_UUID_SUFFIX); g_free (pdu_path); data = g_key_file_to_data (keyfile, &length, NULL); g_file_set_contents (meta_path, data, length, NULL); g_free (data); g_free (meta_path); } void mms_store_meta_close (const char *service_id, const char *uuid, GKeyFile *keyfile, gboolean save) { if (save == TRUE) meta_store_sync (service_id, uuid, keyfile); g_key_file_free (keyfile); } GKeyFile * mms_settings_open (const char *service_id, const char *store) { GKeyFile *keyfile; char *path; if (store == NULL) return NULL; path = mms_store_get_path (service_id, store); if (path == NULL) return NULL; if (create_dirs (path, S_IRUSR | S_IWUSR | S_IXUSR) != 0) { g_critical ("Failed to create path %s", path); g_free (path); return NULL; } keyfile = g_key_file_new (); g_key_file_load_from_file (keyfile, path, 0, NULL); g_free (path); return keyfile; } void mms_settings_sync (const char *service_id, const char *store, GKeyFile *keyfile) { char *path; char *data; gsize length = 0; path = mms_store_get_path (service_id, store); if (path == NULL) return; data = g_key_file_to_data (keyfile, &length, NULL); g_file_set_contents (path, data, length, NULL); g_free (data); g_free (path); } void mms_settings_close (const char *service_id, const char *store, GKeyFile *keyfile, gboolean save) { if (save == TRUE) mms_settings_sync (service_id, store, keyfile); g_key_file_free (keyfile); } mmsd-2.6.0/src/store.h000066400000000000000000000041221456374454100145670ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 * */ #define MMS_SHA1_UUID_LEN 20 #define MMS_META_UUID_SUFFIX ".status" #define MMS_META_UUID_SUFFIX_LEN 7 #define MMS_META_UUID_LEN (MMS_SHA1_UUID_LEN * 2) char *mms_store (const char *service_id, const unsigned char *pdu, unsigned int len); char *mms_store_file (const char *service_id, const char *path); void mms_store_remove (const char *service_id, const char *uuid); char *mms_store_get_path (const char *service_id, const char *uuid); GKeyFile *mms_store_meta_open (const char *service_id, const char *uuid); void mms_store_meta_close (const char *service_id, const char *uuid, GKeyFile *keyfile, gboolean save); void mms_settings_sync (const char *service_id, const char *store, GKeyFile *keyfile); GKeyFile *mms_settings_open (const char *service_id, const char *store); void mms_settings_close (const char *service_id, const char *store, GKeyFile *keyfile, gboolean save); mmsd-2.6.0/src/types.h000066400000000000000000000017041456374454100146020ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 * */ #ifndef FALSE #define FALSE (0) #endif #ifndef TRUE #define TRUE (!FALSE) #endif typedef int mms_bool_t; mmsd-2.6.0/src/version.h.in000066400000000000000000000015721456374454100155330ustar00rootroot00000000000000/* version.h.in * * Copyright 2021-2022 Chris Talbot * * 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 3 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, see . * * Authors: * Chris Talbot * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #define PACKAGE_VCS_VERSION "@VCS_TAG@" mmsd-2.6.0/src/wsputil.c000066400000000000000000000767311456374454100151540ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include "wsputil.h" struct wsp_hex_str_entry { unsigned int type; const char *type_str; }; /* * http://www.wapforum.org/wina/wsp-content-type.htm */ static const char *content_types[] = { "*/*", "text/*", "text/html", "text/plain", "text/x-hdml", "text/x-ttml", "text/x-vCalendar", "text/x-vCard", "text/vnd.wap.wml", "text/vnd.wap.wmlscript", "text/vnd.wap.wta-event", "multipart/*", "multipart/mixed", "multipart/form-data", "multipart/byterantes", "multipart/alternative", "application/*", "application/java-vm", "application/x-www-form-urlencoded", "application/x-hdmlc", "application/vnd.wap.wmlc", "application/vnd.wap.wmlscriptc", "application/vnd.wap.wta-eventc", "application/vnd.wap.uaprof", "application/vnd.wap.wtls-ca-certificate", "application/vnd.wap.wtls-user-certificate", "application/x-x509-ca-cert", "application/x-x509-user-cert", "image/*", "image/gif", "image/jpeg", "image/tiff", "image/png", "image/vnd.wap.wbmp", "application/vnd.wap.multipart.*", "application/vnd.wap.multipart.mixed", "application/vnd.wap.multipart.form-data", "application/vnd.wap.multipart.byteranges", "application/vnd.wap.multipart.alternative", "application/xml", "text/xml", "application/vnd.wap.wbxml", "application/x-x968-cross-cert", "application/x-x968-ca-cert", "application/x-x968-user-cert", "text/vnd.wap.si", "application/vnd.wap.sic", "text/vnd.wap.sl", "application/vnd.wap.slc", "text/vnd.wap.co", "application/vnd.wap.coc", "application/vnd.wap.multipart.related", "application/vnd.wap.sia", "text/vnd.wap.connectivity-xml", "application/vnd.wap.connectivity-wbxml", "application/pkcs7-mime", "application/vnd.wap.hashed-certificate", "application/vnd.wap.signed-certificate", "application/vnd.wap.cert-response", "application/xhtml+xml", "application/wml+xml", "text/css", "application/vnd.wap.mms-message", "application/vnd.wap.rollover-certificate", "application/vnd.wap.locc+wbxml", "application/vnd.wap.loc+xml", "application/vnd.syncml.dm+wbxml", "application/vnd.syncml.dm+xml", "application/vnd.syncml.notification", "application/vnd.wap.xhtml+xml", "application/vnd.wv.csp.cir", "application/vnd.oma.dd+xml", "application/vnd.oma.drm.message", "application/vnd.oma.drm.content", "application/vnd.oma.drm.rights+xml", "application/vnd.oma.drm.rights+wbxml", }; #define LAST_CONTENT_TYPE (sizeof(content_types) / sizeof(const char *)) /* * http://www.wapforum.org/wina/push-app-id.htm */ static const struct wsp_hex_str_entry app_id[] = { { 0x04, "x-wap-application:mms.ua" }, { 0x07, "x-wap-application:syncml.dm" }, { 0x08, "x-wap-application:drm.ua" }, { 0xFF, NULL } }; /* http://www.wapforum.org/wina/wsp-content-type.htm */ static const struct wsp_hex_str_entry extension_mimetypes[] = { { 0x0201, "application/vnd.uplanet.cacheop-wbxml" }, { 0x0202, "application/vnd.uplanet.signal" }, { 0x0203, "application/vnd.uplanet.alert-wbxml" }, { 0x0204, "application/vnd.uplanet.list-wbxml" }, { 0x0205, "application/vnd.uplanet.listcmd-wbxml" }, { 0x0206, "application/vnd.uplanet.channel-wbxml" }, { 0x0207, "application/vnd.uplanet.provisioning-status-uri" }, { 0x0208, "x-wap.multipart/vnd.uplanet.header-set" }, { 0x0209, "application/vnd.uplanet.bearer-choice-wbxml" }, { 0x020A, "application/vnd.phonecom.mmc-wbxml" }, { 0x020B, "application/vnd.nokia.syncset+wbxml" }, { 0x020C, "image/x-up-wpng" }, { 0xFFFF, NULL }, }; static const struct wsp_hex_str_entry charset_assignments[] = { { 0x0000, "*" }, { 0x07EA, "big5" }, { 0x03E8, "iso-10646-ucs-2" }, { 0x0004, "iso-8859-1" }, { 0x0005, "iso-8859-2" }, { 0x0006, "iso-8859-3" }, { 0x0007, "iso-8859-4" }, { 0x0008, "iso-8859-5" }, { 0x0009, "iso-8859-6" }, { 0x000A, "iso-8859-7" }, { 0x000B, "iso-8859-8" }, { 0x000C, "iso-8859-9" }, { 0x0011, "shift_JIS" }, { 0x0003, "us-ascii" }, { 0x006A, "utf-8" }, { 0x03F7, "utf-16" }, { 0xFFFF, NULL }, }; /* * Control Characters 0-8, 10-31 and 127. The tab character is omitted * since it is included in the sep chars array and the most generic TEXT * type of RFC 2616 explicitly allows tabs */ static const char *ctl_chars = "\x01\x02\x03\x04\x05\x06\x07\x08\x0A" "\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14" "\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E" "\x1F\x7F"; static const char *sep_chars = "()<>@,;:\\\"/[]?={} \t"; static const char * decode_text_common (const unsigned char *pdu, unsigned int len, gboolean filter_ctl, gboolean filter_sep, unsigned int *consumed) { unsigned char *c; c = memchr (pdu, '\0', len); if (c == NULL) g_warning ("c is NULL!"); /* RFC 2616 Section 2.2 */ if (filter_ctl && strpbrk ((const char *) pdu, ctl_chars) != NULL) return NULL; if (filter_sep && strpbrk ((const char *) pdu, sep_chars) != NULL) return NULL; if (consumed) { if (c == NULL) return NULL; c += 1; *consumed = c - pdu; } return (const char *) pdu; } const char * wsp_decode_token_text (const unsigned char *pdu, unsigned int len, unsigned int *consumed) { return decode_text_common (pdu, len, TRUE, TRUE, consumed); } const char * wsp_decode_text (const unsigned char *pdu, unsigned int len, unsigned int *consumed) { const char *r; unsigned int fudge = 0; if (*pdu == 127) { pdu++; if (*pdu < 128) return NULL; len -= 1; fudge += 1; } r = decode_text_common (pdu, len, TRUE, FALSE, consumed); if (consumed) *consumed += fudge; return r; } const char * wsp_decode_quoted_string (const unsigned char *pdu, unsigned int len, unsigned int *consumed) { const char *text; text = wsp_decode_text (pdu, len, consumed); if (text == NULL) return NULL; if (*text != '"') return NULL; /* Skip initial quote */ text++; return text; } gboolean wsp_decode_uintvar (const unsigned char *pdu, unsigned int len, unsigned int *out_len, unsigned int *consumed) { unsigned int var; unsigned int i; unsigned int cont; for (i = 0, var = 0, cont = TRUE; i < 5 && i < len && cont; i++) { cont = pdu[i] & 0x80; var = (var << 7) | (pdu[i] & 0x7f); } if (cont) return FALSE; if (out_len) *out_len = var; if (consumed) *consumed = i; return TRUE; } gboolean wsp_decode_integer (const unsigned char *pdu, unsigned int len, unsigned int *out_val, unsigned int *consumed) { unsigned int var; unsigned int i; unsigned int count; if (pdu[0] & 0x80) { var = pdu[0] & 0x7f; count = 1; } else if (pdu[0] <= 30) { unsigned int value_len = *pdu; if (value_len > (len - 1)) return FALSE; if (value_len > sizeof(unsigned int)) return FALSE; var = 0; for (i = 0; i < value_len; i++) var = (var << 8) | pdu[i + 1]; count = value_len + 1; } else return FALSE; if (out_val) *out_val = var; if (consumed) *consumed = count; return TRUE; } gboolean wsp_decode_field (const unsigned char *pdu, unsigned int max, enum wsp_value_type *out_type, const void **out_value, unsigned int *out_len, unsigned int *out_read) { const unsigned char *end = pdu + max; const unsigned char *begin = pdu; unsigned int len; enum wsp_value_type value; unsigned int consumed; if (*pdu <= 30) { len = *pdu; pdu++; if (pdu + len > end) return FALSE; value = WSP_VALUE_TYPE_LONG; } else if (*pdu >= 128) { len = 1; value = WSP_VALUE_TYPE_SHORT; } else if (*pdu == 31) { pdu++; if (pdu == end) return FALSE; if (wsp_decode_uintvar (pdu, end - pdu, &len, &consumed) == FALSE) return FALSE; pdu += consumed; if (pdu + len > end) return FALSE; value = WSP_VALUE_TYPE_LONG; } else { if (decode_text_common (pdu, end - pdu, TRUE, FALSE, &len) == NULL) return FALSE; value = WSP_VALUE_TYPE_TEXT; } if (out_type) *out_type = value; if (out_value) *out_value = pdu; if (out_len) *out_len = len; if (out_read) *out_read = pdu - begin + len; return TRUE; } gboolean wsp_get_well_known_content_type (const char *text, unsigned int *out_val) { unsigned int i; for (i = 0; i < LAST_CONTENT_TYPE; i++) if (g_str_equal (text, content_types[i]) == TRUE) { *out_val = i; return TRUE; } return FALSE; } gboolean wsp_get_well_known_charset (const char *text, unsigned int *out_val) { unsigned int i; for (i = 0; charset_assignments[i].type_str != NULL; i++) if (g_str_equal (charset_assignments[i].type_str, text) == TRUE) { *out_val = charset_assignments[i].type; return TRUE; } return FALSE; } static const char * get_text_entry (unsigned int value, const struct wsp_hex_str_entry *table) { unsigned int i; for (i = 0; table[i].type_str != NULL; i++) if (table[i].type == value) return table[i].type_str; return NULL; } gboolean wsp_decode_content_type (const unsigned char *pdu, unsigned int max, const void **out_value, unsigned int *out_read, unsigned int *out_param_len) { unsigned int param_len = 0; unsigned int len; const void *data; enum wsp_value_type value_type; unsigned int consumed; if (wsp_decode_field (pdu, max, &value_type, &data, &len, &consumed) != TRUE) return FALSE; if (value_type == WSP_VALUE_TYPE_LONG) { unsigned int media_len; unsigned int value_len; if (wsp_decode_field (data, max, &value_type, &data, &value_len, &media_len) != TRUE) return FALSE; param_len = len - media_len; consumed -= param_len; /* Handle Well-Known-Media Long-Integer case */ if (value_type == WSP_VALUE_TYPE_LONG) { const unsigned char *pdu_val = data; unsigned int var = 0; unsigned int i; if (value_len > sizeof(unsigned int)) return FALSE; for (i = 0; i < value_len; i++) var = (var << 8) | pdu_val[i + 1]; data = get_text_entry (var, extension_mimetypes); } } if (value_type == WSP_VALUE_TYPE_SHORT) { const unsigned char *pdu_val = data; unsigned int val; val = *pdu_val & 0x7f; if (val >= LAST_CONTENT_TYPE) return FALSE; data = content_types[val]; } if (out_value) *out_value = data; if (out_read) *out_read = consumed; if (out_param_len) *out_param_len = param_len; return TRUE; } gboolean wsp_decode_application_id (struct wsp_header_iter *iter, const void **out_value) { const unsigned char *pdu_val = wsp_header_iter_get_val (iter); unsigned int val; unsigned int val_len; unsigned int i; switch (wsp_header_iter_get_val_type (iter)) { case WSP_VALUE_TYPE_TEXT: if (out_value) *out_value = pdu_val; break; /* * Well-known field values MUST be encoded using the * compact binary formats */ case WSP_VALUE_TYPE_SHORT: val = *pdu_val & 0x7f; if (out_value) *out_value = get_text_entry (val, app_id); break; case WSP_VALUE_TYPE_LONG: val_len = wsp_header_iter_get_val_len (iter); if (val_len > 2) return FALSE; for (i = 0, val = 0; i < val_len && i < sizeof(val); i++) val = (val << 8) | pdu_val[i]; if (out_value) *out_value = get_text_entry (val, app_id); break; default: g_warning ("Unhandled case"); return FALSE; } return TRUE; } static inline gboolean is_content_type_header (const unsigned char *pdu, unsigned char code_page, unsigned int flags) { /* Check for MMS Content-Type header */ if (flags & WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART) if (code_page == 1 && *pdu == 0x84) return TRUE; /* Check for WSP default Content-Type header */ if (code_page == 1 && *pdu == 0x91) return TRUE; return FALSE; } gboolean wsp_encode_uintvar (unsigned int value, unsigned char *dest, unsigned int dest_size, unsigned int *written) { unsigned char d[5]; unsigned int count = 0; /* Separate into 7-bit chunks, LS first */ while (value || !count) { d[count++] = value & 0x7F; value = value >> 7; } if (count > dest_size) return FALSE; *written = count; /* * Output to stream, MS first! * 0x80 flag = "continue". LS byte does not have this flag. */ while (--count) *dest++ = d[count] | 0x80; *dest = d[count]; return TRUE; } gboolean wsp_encode_value_length (unsigned int len, unsigned char *dest, unsigned int dest_size, unsigned int *written) { if (dest_size < 1) return FALSE; if (len <= 30) { *dest = len; *written = 1; return TRUE; } /* 31 is escape for variable length int */ *dest++ = 31; dest_size--; if (wsp_encode_uintvar (len, dest, dest_size, written) == FALSE) return FALSE; *written += 1; return TRUE; } gboolean wsp_encode_integer (unsigned int value, unsigned char *dest, unsigned int dest_size, unsigned int *written) { unsigned char moi[sizeof(unsigned int)]; unsigned int count; if (dest_size < 1) return FALSE; if (value < 0x80) { *dest = value | 0x80; *written = 1; return TRUE; } for (count = 0; count < sizeof(unsigned int) && value; count++) { moi[count] = value & 0xFF; value = value >> 8; } if (count + 1 > dest_size) return FALSE; *written = count + 1; *dest++ = count; for (; count > 0; count--) *dest++ = moi[count - 1]; return TRUE; } void wsp_header_iter_init (struct wsp_header_iter *iter, const unsigned char *pdu, unsigned int len, unsigned int flags) { iter->pdu = pdu; iter->pos = 0; iter->max = len; iter->code_page = 1; iter->flags = flags; } gboolean wsp_header_iter_next (struct wsp_header_iter *iter) { const unsigned char *pdu = iter->pdu + iter->pos; const unsigned char *end = iter->pdu + iter->max; enum wsp_header_type header; const void *hdr; unsigned int consumed; if (pdu == end) return FALSE; /* * 8.4.2.6 Header * The following rules are used to encode headers. * Header = Message-header | Shift-sequence * Shift-sequence = (Shift-delimiter Page-identity) | * Short-cut-shift-delimiter * Shift-delimiter = * Page-identity = * Short-cut-shift-delimiter = * Message-header = Well-known-header | Application-header * Well-known-header = Well-known-field-name Wap-value * Application-header = Token-text Application-specific-value * Well-known-field-name = Short-integer * Application-specific-value = Text-string */ while (*pdu == 127 || (*pdu >= 1 && *pdu <= 31)) { if (iter->flags & WSP_HEADER_ITER_FLAG_REJECT_CP) return FALSE; if (*pdu == 127) { pdu++; if (pdu == end) return FALSE; iter->code_page = *pdu; pdu++; } else iter->code_page = *pdu++; } if (pdu == end) return FALSE; if (*pdu >= 0x80) { if (is_content_type_header (pdu, iter->code_page, iter->flags)) return FALSE; header = WSP_HEADER_TYPE_WELL_KNOWN; hdr = pdu; pdu++; } else { if (wsp_decode_token_text (pdu, end - pdu, &consumed) == NULL) return FALSE; header = WSP_HEADER_TYPE_APPLICATION; hdr = pdu; pdu += consumed; } if (pdu == end) return FALSE; /* * Section 8.4.1.2 of WAP-230: * If the field name is encoded in text format, textual values MUST * be used. */ if ((*pdu < 32 || *pdu > 127) && header == WSP_HEADER_TYPE_APPLICATION) return FALSE; if (wsp_decode_field (pdu, end - pdu, &iter->value_type, &iter->value, &iter->len, &consumed) == FALSE) return FALSE; iter->header_type = header; iter->header = hdr; iter->pos = pdu + consumed - iter->pdu; return TRUE; } unsigned char wsp_header_iter_get_code_page (struct wsp_header_iter *iter) { return iter->code_page; } gboolean wsp_header_iter_at_end (struct wsp_header_iter *iter) { if (iter->pos == iter->max) return TRUE; return FALSE; } gboolean wsp_header_iter_is_multipart (struct wsp_header_iter *iter) { const unsigned char *pdu = iter->pdu + iter->pos; return is_content_type_header (pdu, iter->code_page, iter->flags); } enum wsp_header_type wsp_header_iter_get_hdr_type (struct wsp_header_iter *iter) { return iter->header_type; } const unsigned char * wsp_header_iter_get_pdu (struct wsp_header_iter *iter) { return iter->pdu; } const void * wsp_header_iter_get_hdr (struct wsp_header_iter *iter) { return iter->header; } enum wsp_value_type wsp_header_iter_get_val_type (struct wsp_header_iter *iter) { return iter->value_type; } const void * wsp_header_iter_get_val (struct wsp_header_iter *iter) { return iter->value; } unsigned int wsp_header_iter_get_val_len (struct wsp_header_iter *iter) { return iter->len; } gboolean wsp_multipart_iter_init (struct wsp_multipart_iter *mi, struct wsp_header_iter *hi, const void **out_content_type, unsigned int *out_content_type_len) { const unsigned char *pdu = hi->pdu + hi->pos; const unsigned char *end = hi->pdu + hi->max; unsigned int consumed; unsigned int ct_len; /* Assume content-type header is well known */ if (pdu + 1 > end) return FALSE; pdu++; /* Consume the Content-Type value of Content-Type header */ if (wsp_decode_field (pdu, end - pdu, NULL, NULL, NULL, &consumed) == FALSE) return FALSE; pdu += consumed; ct_len = consumed; /* * Consume the uinvar specifying the number of parts. This is set to * 0 in later specifications and can be safely ignored */ if (wsp_decode_uintvar (pdu, end - pdu, NULL, &consumed) == FALSE) return FALSE; memset (mi, 0, sizeof(*mi)); mi->pdu = hi->pdu + hi->pos; mi->max = hi->max - hi->pos; mi->pos = pdu + consumed - mi->pdu; if (out_content_type) *out_content_type = mi->pdu + 1; if (out_content_type_len) *out_content_type_len = ct_len; return TRUE; } gboolean wsp_multipart_iter_next (struct wsp_multipart_iter *mi) { const unsigned char *pdu = mi->pdu + mi->pos; const unsigned char *end = mi->pdu + mi->max; unsigned int headers_len; unsigned int body_len; unsigned int consumed; if (wsp_decode_uintvar (pdu, end - pdu, &headers_len, &consumed) == FALSE) return FALSE; pdu += consumed; if (wsp_decode_uintvar (pdu, end - pdu, &body_len, &consumed) == FALSE) return FALSE; pdu += consumed; if (pdu + headers_len + body_len > end) return FALSE; /* Consume the Content-Type value */ if (wsp_decode_field (pdu, end - pdu, NULL, NULL, NULL, &consumed) == FALSE) return FALSE; mi->content_type = pdu; mi->content_type_len = consumed; mi->headers = pdu + consumed; mi->headers_len = headers_len - consumed; mi->body = pdu + headers_len; mi->body_len = body_len; mi->pos = pdu - mi->pdu + headers_len + body_len; return TRUE; } const void * wsp_multipart_iter_get_content_type (struct wsp_multipart_iter *mi) { return mi->content_type; } unsigned int wsp_multipart_iter_get_content_type_len ( struct wsp_multipart_iter *mi) { return mi->content_type_len; } const void * wsp_multipart_iter_get_hdr (struct wsp_multipart_iter *mi) { return mi->headers; } unsigned int wsp_multipart_iter_get_hdr_len (struct wsp_multipart_iter *mi) { return mi->headers_len; } const void * wsp_multipart_iter_get_body (struct wsp_multipart_iter *mi) { return mi->body; } unsigned int wsp_multipart_iter_get_body_len (struct wsp_multipart_iter *mi) { return mi->body_len; } gboolean wsp_multipart_iter_close (struct wsp_multipart_iter *mi, struct wsp_header_iter *hi) { if (mi->pos != mi->max) return FALSE; hi->pos += mi->pos; return TRUE; } void wsp_parameter_iter_init (struct wsp_parameter_iter *pi, const unsigned char *pdu, unsigned int len) { pi->pdu = pdu; pi->max = len; pi->pos = 0; } gboolean wsp_parameter_iter_next (struct wsp_parameter_iter *pi, struct wsp_parameter *out) { const unsigned char *pdu = pi->pdu + pi->pos; const unsigned char *end = pi->pdu + pi->max; unsigned int token; unsigned int consumed; const char *untyped; const char *value; /* Well known parameter token */ if (wsp_decode_integer (pdu, end - pdu, &token, &consumed) == TRUE) { pdu += consumed; switch (token) { case WSP_PARAMETER_TYPE_LEVEL: case WSP_PARAMETER_TYPE_DIFFERENCES: if (*pdu & 0x80) { unsigned int i = *pdu & 0x7f; pdu += 1; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_INT; out->integer = i; } return TRUE; } /* Continue to the string case */ /* fall through */ case WSP_PARAMETER_TYPE_NAME_DEFUNCT: case WSP_PARAMETER_TYPE_FILENAME_DEFUNCT: case WSP_PARAMETER_TYPE_START_DEFUNCT: case WSP_PARAMETER_TYPE_START_INFO_DEFUNCT: case WSP_PARAMETER_TYPE_COMMENT_DEFUNCT: case WSP_PARAMETER_TYPE_DOMAIN_DEFUNCT: case WSP_PARAMETER_TYPE_PATH_DEFUNCT: case WSP_PARAMETER_TYPE_NAME: case WSP_PARAMETER_TYPE_FILENAME: case WSP_PARAMETER_TYPE_START: case WSP_PARAMETER_TYPE_START_INFO: case WSP_PARAMETER_TYPE_COMMENT: case WSP_PARAMETER_TYPE_DOMAIN: case WSP_PARAMETER_TYPE_PATH: case WSP_PARAMETER_TYPE_MAC: { const char *text = wsp_decode_text (pdu, end - pdu, &consumed); if (text == NULL) return FALSE; pdu += consumed; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_TEXT; out->text = text; } return TRUE; } case WSP_PARAMETER_TYPE_TYPE: case WSP_PARAMETER_TYPE_SIZE: case WSP_PARAMETER_TYPE_MAX_AGE: { unsigned int i; if (wsp_decode_integer (pdu, end - pdu, &i, &consumed) == FALSE) return FALSE; pdu += consumed; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_INT; out->integer = i; } return TRUE; } case WSP_PARAMETER_TYPE_PADDING: case WSP_PARAMETER_TYPE_SEC: if ((*pdu & 0x80) == 0) return FALSE; pdu += 1; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_INT; out->integer = *pdu & 0x7f; } return TRUE; case WSP_PARAMETER_TYPE_CREATION_DATE: case WSP_PARAMETER_TYPE_MODIFICATION_DATE: case WSP_PARAMETER_TYPE_READ_DATE: { unsigned int i; if (wsp_decode_integer (pdu, end - pdu, &i, &consumed) == FALSE) return FALSE; pdu += consumed; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_DATE; out->integer = i; } return TRUE; } case WSP_PARAMETER_TYPE_SECURE: if (*pdu != 0) return FALSE; pdu += 1; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_TEXT; out->text = (const char *) pdu - 1; } return TRUE; case WSP_PARAMETER_TYPE_CHARSET: { unsigned int i; const char *charset; if (wsp_decode_integer (pdu, end - pdu, &i, &consumed) == FALSE) return FALSE; charset = get_text_entry (i, charset_assignments); if (charset == NULL) return FALSE; pdu += consumed; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_TEXT; out->text = charset; } return TRUE; } case WSP_PARAMETER_TYPE_CONTENT_TYPE: { const char *ct; if (*pdu & 0x80) { unsigned int i = *pdu & 0x7f; if (i >= LAST_CONTENT_TYPE) return FALSE; ct = content_types[i]; pdu += 1; } else if ((ct = wsp_decode_text (pdu, end - pdu, &consumed))) pdu += consumed; else return FALSE; pi->pos = pdu - pi->pdu; if (out) { out->type = token; out->value = WSP_PARAMETER_VALUE_TEXT; out->text = ct; } return TRUE; } /* TODO */ case WSP_PARAMETER_TYPE_Q: default: return FALSE; } } untyped = wsp_decode_text (pdu, end - pdu, &consumed); if (untyped == NULL) return FALSE; pdu += consumed; if (wsp_decode_integer (pdu, end - pdu, &token, &consumed) == TRUE) { pdu += consumed; pi->pos = pdu - pi->pdu; out->type = WSP_PARAMETER_TYPE_UNTYPED; out->value = WSP_PARAMETER_VALUE_INT; out->integer = token; out->untyped = untyped; return TRUE; } value = wsp_decode_text (pdu, end - pdu, &consumed); if (value == NULL) return FALSE; pdu += consumed; pi->pos = pdu - pi->pdu; out->type = WSP_PARAMETER_TYPE_UNTYPED; out->value = WSP_PARAMETER_VALUE_TEXT; out->text = value; out->untyped = untyped; return TRUE; } static const char * decode_token (char *buf, gboolean accept_quotes, unsigned int *out_consumed, char *out_terminator) { unsigned int pos = 0; unsigned int start; unsigned int end; char *endp; char terminator = '\0'; /* Skip leading space */ while (buf[pos] == ' ' || buf[pos] == '\t') pos += 1; if (buf[pos] == '\0') return NULL; start = pos; if (buf[pos] == '"') { if (accept_quotes == FALSE) return NULL; pos += 1; start = pos; endp = strchr (buf + pos, '"'); if (endp == NULL) return NULL; pos = endp - buf; end = pos; pos += 1; } else { endp = strpbrk (buf + pos, sep_chars); if (endp == NULL) pos = strlen (buf); else pos = endp - buf; end = pos; } while (buf[pos] == ' ' || buf[pos] == '\t') pos += 1; if (buf[pos] != '\0') { terminator = buf[pos]; pos += 1; } buf[end] = '\0'; if (strpbrk (buf + start, ctl_chars) != NULL) return NULL; *out_consumed = pos; *out_terminator = terminator; return buf + start; } gboolean wsp_text_header_iter_init (struct wsp_text_header_iter *iter, const char *hdr) { unsigned int len = strlen (hdr); char terminator; unsigned int consumed; const char *key; const char *value; if (len > MAX_TEXT_HEADER_SIZE) return FALSE; memcpy (iter->hdr, hdr, len); iter->hdr[len] = '\0'; iter->pos = 0; iter->key = NULL; iter->value = NULL; key = decode_token (iter->hdr, FALSE, &consumed, &terminator); if (key == NULL) return FALSE; if (terminator != ':') return FALSE; len = consumed; value = decode_token (iter->hdr + len, TRUE, &consumed, &terminator); if (value == NULL) return FALSE; if (terminator != '\0' && terminator != ';') return FALSE; len += consumed; iter->key = key; iter->value = value; iter->pos = len; return TRUE; } gboolean wsp_text_header_iter_param_next (struct wsp_text_header_iter *iter) { unsigned int pos = iter->pos; char terminator; unsigned int consumed; const char *key; const char *value; key = decode_token (iter->hdr + pos, FALSE, &consumed, &terminator); if (key == NULL) return FALSE; /* Empty value */ if (terminator == ';' || terminator == '\0') { iter->key = key; iter->value = NULL; iter->pos += consumed; return TRUE; } if (terminator != '=') return FALSE; pos += consumed; value = decode_token (iter->hdr + pos, TRUE, &consumed, &terminator); if (value == NULL) return FALSE; if (terminator != '\0' && terminator != ';') return FALSE; pos += consumed; iter->key = key; iter->value = value; iter->pos = pos; return TRUE; } const char * wsp_text_header_iter_get_key (struct wsp_text_header_iter *iter) { return iter->key; } const char * wsp_text_header_iter_get_value (struct wsp_text_header_iter *iter) { return iter->value; } mmsd-2.6.0/src/wsputil.h000066400000000000000000000223411456374454100151450ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 * */ enum wsp_header_iter_flag { WSP_HEADER_ITER_FLAG_REJECT_CP = 0x1, WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART = 0x2, }; enum wsp_header_type { WSP_HEADER_TYPE_WELL_KNOWN, WSP_HEADER_TYPE_APPLICATION }; enum wsp_value_type { WSP_VALUE_TYPE_LONG, WSP_VALUE_TYPE_SHORT, WSP_VALUE_TYPE_TEXT, }; enum wsp_header_token { WSP_HEADER_TOKEN_APP_ID = 0x2F, }; enum wsp_parameter_type { WSP_PARAMETER_TYPE_Q = 0x00, WSP_PARAMETER_TYPE_CHARSET = 0x01, WSP_PARAMETER_TYPE_LEVEL = 0x02, WSP_PARAMETER_TYPE_TYPE = 0x03, WSP_PARAMETER_TYPE_NAME_DEFUNCT = 0x05, WSP_PARAMETER_TYPE_FILENAME_DEFUNCT = 0x06, WSP_PARAMETER_TYPE_DIFFERENCES = 0x07, WSP_PARAMETER_TYPE_PADDING = 0x08, WSP_PARAMETER_TYPE_CONTENT_TYPE = 0x09, WSP_PARAMETER_TYPE_START_DEFUNCT = 0x0A, WSP_PARAMETER_TYPE_START_INFO_DEFUNCT = 0x0B, WSP_PARAMETER_TYPE_COMMENT_DEFUNCT = 0x0C, WSP_PARAMETER_TYPE_DOMAIN_DEFUNCT = 0x0D, WSP_PARAMETER_TYPE_MAX_AGE = 0x0E, WSP_PARAMETER_TYPE_PATH_DEFUNCT = 0x0F, WSP_PARAMETER_TYPE_SECURE = 0x10, WSP_PARAMETER_TYPE_SEC = 0x11, WSP_PARAMETER_TYPE_MAC = 0x12, WSP_PARAMETER_TYPE_CREATION_DATE = 0x13, WSP_PARAMETER_TYPE_MODIFICATION_DATE = 0x14, WSP_PARAMETER_TYPE_READ_DATE = 0x15, WSP_PARAMETER_TYPE_SIZE = 0x16, WSP_PARAMETER_TYPE_NAME = 0x17, WSP_PARAMETER_TYPE_FILENAME = 0x18, WSP_PARAMETER_TYPE_START = 0x19, WSP_PARAMETER_TYPE_START_INFO = 0x1A, WSP_PARAMETER_TYPE_COMMENT = 0x1B, WSP_PARAMETER_TYPE_DOMAIN = 0x1C, WSP_PARAMETER_TYPE_PATH = 0x1D, WSP_PARAMETER_TYPE_UNTYPED = 0xFF, }; enum wsp_parameter_value { WSP_PARAMETER_VALUE_TEXT, WSP_PARAMETER_VALUE_INT, WSP_PARAMETER_VALUE_DATE, WSP_PARAMETER_VALUE_Q, }; struct wsp_parameter { enum wsp_parameter_type type; enum wsp_parameter_value value; const char *untyped; union { const char *text; unsigned int integer; time_t date; float q; }; }; struct wsp_header_iter { const unsigned char *pdu; unsigned int max; unsigned int pos; unsigned int flags; unsigned char code_page; enum wsp_header_type header_type; const void *header; enum wsp_value_type value_type; const void *value; unsigned int len; }; struct wsp_multipart_iter { const unsigned char *pdu; unsigned int max; unsigned int pos; const void *content_type; const void *headers; const void *body; unsigned int content_type_len; unsigned int headers_len; unsigned int body_len; }; struct wsp_parameter_iter { const unsigned char *pdu; unsigned int max; unsigned int pos; }; #define MAX_TEXT_HEADER_SIZE 4096 struct wsp_text_header_iter { char hdr[MAX_TEXT_HEADER_SIZE + 1]; unsigned int pos; const char *key; const char *value; }; gboolean wsp_decode_uintvar (const unsigned char *pdu, unsigned int len, unsigned int *out_len, unsigned int *consumed); gboolean wsp_decode_integer (const unsigned char *pdu, unsigned int len, unsigned int *out_val, unsigned int *consumed); gboolean wsp_decode_field (const unsigned char *pdu, unsigned int max, enum wsp_value_type *out_type, const void **out_value, unsigned int *out_len, unsigned int *consumed); gboolean wsp_get_well_known_content_type (const char *text, unsigned int *out_val); gboolean wsp_get_well_known_charset (const char *text, unsigned int *out_val); const char *wsp_decode_token_text (const unsigned char *pdu, unsigned int len, unsigned int *consumed); const char *wsp_decode_text (const unsigned char *pdu, unsigned int len, unsigned int *consumed); const char *wsp_decode_quoted_string (const unsigned char *pdu, unsigned int len, unsigned int *consumed); gboolean wsp_decode_content_type (const unsigned char *pdu, unsigned int max, const void **out_value, unsigned int *out_read, unsigned int *out_param_len); gboolean wsp_decode_application_id (struct wsp_header_iter *iter, const void **out_value); gboolean wsp_encode_uintvar (unsigned int value, unsigned char *dest, unsigned int dest_size, unsigned int *written); gboolean wsp_encode_value_length (unsigned int len, unsigned char *dest, unsigned int dest_size, unsigned int *written); gboolean wsp_encode_integer (unsigned int value, unsigned char *dest, unsigned int dest_size, unsigned int *written); void wsp_header_iter_init (struct wsp_header_iter *iter, const unsigned char *pdu, unsigned int len, unsigned int flags); gboolean wsp_header_iter_next (struct wsp_header_iter *iter); unsigned char wsp_header_iter_get_code_page (struct wsp_header_iter *iter); gboolean wsp_header_iter_at_end (struct wsp_header_iter *iter); gboolean wsp_header_iter_is_multipart (struct wsp_header_iter *iter); enum wsp_header_type wsp_header_iter_get_hdr_type (struct wsp_header_iter *iter); const unsigned char *wsp_header_iter_get_pdu (struct wsp_header_iter *iter); const void *wsp_header_iter_get_hdr (struct wsp_header_iter *iter); enum wsp_value_type wsp_header_iter_get_val_type (struct wsp_header_iter *iter); const void *wsp_header_iter_get_val (struct wsp_header_iter *iter); unsigned int wsp_header_iter_get_val_len (struct wsp_header_iter *iter); gboolean wsp_multipart_iter_init (struct wsp_multipart_iter *mi, struct wsp_header_iter *hi, const void **out_content_type, unsigned int *out_content_type_len); gboolean wsp_multipart_iter_next (struct wsp_multipart_iter *mi); const void *wsp_multipart_iter_get_content_type (struct wsp_multipart_iter *mi); unsigned int wsp_multipart_iter_get_content_type_len ( struct wsp_multipart_iter *mi); const void *wsp_multipart_iter_get_hdr (struct wsp_multipart_iter *mi); unsigned int wsp_multipart_iter_get_hdr_len (struct wsp_multipart_iter *mi); const void *wsp_multipart_iter_get_body (struct wsp_multipart_iter *mi); unsigned int wsp_multipart_iter_get_body_len (struct wsp_multipart_iter *mi); gboolean wsp_multipart_iter_close (struct wsp_multipart_iter *mi, struct wsp_header_iter *hi); void wsp_parameter_iter_init (struct wsp_parameter_iter *pi, const unsigned char *pdu, unsigned int len); gboolean wsp_parameter_iter_next (struct wsp_parameter_iter *pi, struct wsp_parameter *out_param); gboolean wsp_text_header_iter_init (struct wsp_text_header_iter *iter, const char *hdr); gboolean wsp_text_header_iter_param_next (struct wsp_text_header_iter *iter); const char *wsp_text_header_iter_get_key (struct wsp_text_header_iter *iter); const char *wsp_text_header_iter_get_value (struct wsp_text_header_iter *iter); mmsd-2.6.0/test/000077500000000000000000000000001456374454100134535ustar00rootroot00000000000000mmsd-2.6.0/test/delete-message000077500000000000000000000004301456374454100162620ustar00rootroot00000000000000#!/usr/bin/python import sys import dbus if (len(sys.argv) < 2): print "Usage: %s " % (sys.argv[0]) sys.exit(1) bus = dbus.SessionBus() message = dbus.Interface(bus.get_object('org.ofono.mms', sys.argv[1]), 'org.ofono.mms.Message') message.Delete() mmsd-2.6.0/test/get-conversation000077500000000000000000000013211456374454100166650ustar00rootroot00000000000000#!/usr/bin/python import sys import dbus if (len(sys.argv) < 3): print "Usage: %s '' " % (sys.argv[0]) print "Sample: get-conversation '555-1234' 0" sys.exit(1) bus = dbus.SessionBus() manager = dbus.Interface(bus.get_object('org.ofono.mms', '/org/ofono/mms'), 'org.ofono.mms.Manager') services = manager.GetServices() path = services[0][0] service = dbus.Interface(bus.get_object('org.ofono.mms', path), 'org.ofono.mms.Service') messages = service.GetConversation(sys.argv[1], sys.argv[2]) for entry in messages: path = entry[0] properties = entry[1] print "[ %s ]" % (path) for key in properties.keys(): val = str(properties[key]) print " %s = %s" % (key, val) print mmsd-2.6.0/test/get-messages000077500000000000000000000010371456374454100157660ustar00rootroot00000000000000#!/usr/bin/python import dbus bus = dbus.SessionBus() manager = dbus.Interface(bus.get_object('org.ofono.mms', '/org/ofono/mms'), 'org.ofono.mms.Manager') services = manager.GetServices() path = services[0][0] service = dbus.Interface(bus.get_object('org.ofono.mms', path), 'org.ofono.mms.Service') messages = service.GetMessages() for entry in messages: path = entry[0] properties = entry[1] print "[ %s ]" % (path) for key in properties.keys(): val = str(properties[key]) print " %s = %s" % (key, val) print mmsd-2.6.0/test/get-services000077500000000000000000000006071456374454100160040ustar00rootroot00000000000000#!/usr/bin/python import dbus bus = dbus.SessionBus() manager = dbus.Interface(bus.get_object('org.ofono.mms', '/org/ofono/mms'), 'org.ofono.mms.Manager') services = manager.GetServices() for entry in services: path = entry[0] properties = entry[1] print "[ %s ]" % (path) for key in properties.keys(): val = str(properties[key]) print " %s = %s" % (key, val) print mmsd-2.6.0/test/list-services000077500000000000000000000010171456374454100161740ustar00rootroot00000000000000#!/usr/bin/python import dbus bus = dbus.SessionBus() manager = dbus.Interface(bus.get_object('org.ofono.mms', '/org/ofono/mms'), 'org.ofono.mms.Manager') services = manager.GetServices() for path, properties in services: print "[ %s ]" % (path) object = dbus.Interface(bus.get_object('org.ofono.mms', path), 'org.ofono.mms.Service') try: properties = object.GetProperties() except: continue for key in properties.keys(): val = str(properties[key]) print " %s = %s" % (key, val) print mmsd-2.6.0/test/mark-message-read000077500000000000000000000004321456374454100166650ustar00rootroot00000000000000#!/usr/bin/python import sys import dbus if (len(sys.argv) < 2): print "Usage: %s " % (sys.argv[0]) sys.exit(1) bus = dbus.SessionBus() message = dbus.Interface(bus.get_object('org.ofono.mms', sys.argv[1]), 'org.ofono.mms.Message') message.MarkRead() mmsd-2.6.0/test/monitor-mms000077500000000000000000000045101456374454100156620ustar00rootroot00000000000000#!/usr/bin/python import gobject import dbus import dbus.mainloop.glib def property_changed(name, value, path, interface): iface = interface[interface.rfind(".") + 1:] print "{%s} [%s] %s = %s" % (iface, path, name, value) def service_added(name, value, member, path, interface): iface = interface[interface.rfind(".") + 1:] print "{%s} [%s] %s %s" % (iface, name, member, value) def service_removed(name, member, path, interface): iface = interface[interface.rfind(".") + 1:] print "{%s} [%s] %s" % (iface, name, member) def message_added(name, value, member, path, interface): iface = interface[interface.rfind(".") + 1:] print "{%s} [%s] %s %s" % (iface, name, member, value) def message_removed(name, value, member, path, interface): iface = interface[interface.rfind(".") + 1:] print "{%s} [%s] %s %s" % (iface, name, member, value) def property_changed(name, value, member, path, interface): iface = interface[interface.rfind(".") + 1:] print "{%s} [%s] %s %s" % (iface, name, member, value) if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() bus.add_signal_receiver(property_changed, bus_name="org.ofono.mms", signal_name = "PropertyChanged", path_keyword="path", interface_keyword="interface") bus.add_signal_receiver(service_added, bus_name="org.ofono.mms", signal_name = "ServiceAdded", member_keyword="member", path_keyword="path", interface_keyword="interface") bus.add_signal_receiver(service_removed, bus_name="org.ofono.mms", signal_name = "ServiceRemoved", member_keyword="member", path_keyword="path", interface_keyword="interface") bus.add_signal_receiver(message_added, bus_name="org.ofono.mms", signal_name = "MessageAdded", member_keyword="member", path_keyword="path", interface_keyword="interface") bus.add_signal_receiver(message_removed, bus_name="org.ofono.mms", signal_name = "MessageRemoved", member_keyword="member", path_keyword="path", interface_keyword="interface") bus.add_signal_receiver(property_changed, bus_name="org.ofono.mms", signal_name = "PropertyChanged", member_keyword="member", path_keyword="path", interface_keyword="interface") mainloop = gobject.MainLoop() mainloop.run() mmsd-2.6.0/test/sanitized_send_mms_subject.py000066400000000000000000000010461456374454100214240ustar00rootroot00000000000000#!/usr/bin/python import pydbus import gi.repository from pydbus import SessionBus from gi.repository import GLib bus = SessionBus() TestServer = bus.get("org.ofono.mms", "/org/ofono/mms/modemmanager") options = GLib.Variant('a{sv}', { 'DeliveryReport': GLib.Variant('b', False), 'Subject': GLib.Variant('s', "Test Subject!"), }) TestServer.SendMessage(["+1XXXXXXXXXX"], options, [("cid-1", "text/plain", "/home/mobian/Test-MMS/mmsattachments/text.txt"), ("cid-2", "image/jpeg", "/home/mobian/Test-MMS/mmsattachments/JPG1200x1600.jpg")]) mmsd-2.6.0/test/send-message000077500000000000000000000031531456374454100157560ustar00rootroot00000000000000#!/usr/bin/python import sys import dbus import csv if (len(sys.argv) < 4): print("Usage: {}"\ " ,..."\ " "\ " <,,>,..."\ .format(sys.argv[0])) print("Sample(Related): {}"\ " \"+33611111111,+33622222222\""\ " \"smil.txt\""\ " \"cid-1,text/plain,text.txt\""\ " \"cid-2,image/jpeg,image.jpg\""\ .format(sys.argv[0])) print("Sample(Mixed): {}"\ " \"+33611111111,+33622222222\""\ " \"\""\ " \"cid-1,text/plain,text.txt\""\ " \"cid-2,image/jpeg,image.jpg\""\ .format(sys.argv[0])) sys.exit(1) bus = dbus.SessionBus() manager = dbus.Interface(bus.get_object('org.ofono.mms', '/org/ofono/mms'), 'org.ofono.mms.Manager') services = manager.GetServices() path = services[0][0] service = dbus.Interface(bus.get_object('org.ofono.mms', path), 'org.ofono.mms.Service') recipients = dbus.Array([],signature=dbus.Signature('s')) reader = csv.reader([sys.argv[1]]) for r in reader: print("Recipient list: {}".format(r)) for i in r: recipients.append(dbus.String(i)) if sys.argv[2] == "": print("Send MMS as Mixed") smil = "" else: print("Send MMS as Related") print("Smil path: {}".format(sys.argv[2])) file = open(sys.argv[2],"r") smil = dbus.String(file.read()) attachments = dbus.Array([],signature=dbus.Signature('(sss)')) for a in sys.argv[3:]: print("Attachment: ({})".format(a)) reader = csv.reader([a]) for r in reader: attachments.append(dbus.Struct((dbus.String(r[0]), dbus.String(r[1]), dbus.String(r[2]) ), signature=None)) path = service.SendMessage(recipients, smil, attachments) print(path) mmsd-2.6.0/test/set-use-delivery-reports000077500000000000000000000010661456374454100203060ustar00rootroot00000000000000#!/usr/bin/python import sys import dbus bus = dbus.SessionBus() manager = dbus.Interface(bus.get_object('org.ofono.mms', '/org/ofono/mms'), 'org.ofono.mms.Manager') services = manager.GetServices() path = services[0][0] service = dbus.Interface(bus.get_object('org.ofono.mms', path), 'org.ofono.mms.Service') if len(sys.argv) > 1: allowed = dbus.Boolean(int(sys.argv[1])) else: allowed = dbus.Boolean(1) print "Setting delivery report use for %s...(UseDeliveryReports=%d)" %\ (path, allowed) service.SetProperty("UseDeliveryReports", allowed) mmsd-2.6.0/tools/000077500000000000000000000000001456374454100136345ustar00rootroot00000000000000mmsd-2.6.0/tools/create-hex-array.c000066400000000000000000000036101456374454100171410ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include #include #include static int dump_file (const char *pathname) { struct stat st; unsigned char *map; size_t size, i; int fd; fd = open (pathname, O_RDONLY); if (fd < 0) return -1; if (fstat (fd, &st) < 0) { close (fd); return -1; } size = st.st_size; map = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (!map || map == MAP_FAILED) { close (fd); return -1; } printf ("static const unsigned char msg[] = {"); for (i = 0; i < size; i++) { if ((i % 8) == 0) printf ("\n\t\t\t\t"); printf ("0x%02X, ", map[i]); } printf ("\n};\n"); munmap (map, size); close (fd); return 0; } int main (int argc, char *argv[]) { if (argc < 2) { fprintf (stderr, "Missing input\n"); return 1; } if (dump_file (argv[1]) < 0) { fprintf (stderr, "Failed to open file\n"); return 1; } return 0; } mmsd-2.6.0/tools/decode-mms.c000066400000000000000000000257141456374454100160260ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include #include #include #include #include #include "wsputil.h" static const unsigned char mms_msg1[] = { 0x8C, 0x82, 0x98, 0x4F, 0x67, 0x51, 0x4B, 0x4B, 0x42, 0x00, 0x8D, 0x90, 0x89, 0x08, 0x80, 0x45, 0x72, 0x6F, 0x74, 0x69, 0x6B, 0x00, 0x96, 0x50, 0x69, 0x6E, 0x2D, 0x55, 0x70, 0x73, 0x00, 0x8A, 0x80, 0x8E, 0x02, 0x40, 0x00, 0x88, 0x05, 0x81, 0x03, 0x03, 0xF4, 0x80, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x65, 0x70, 0x73, 0x33, 0x2E, 0x64, 0x65, 0x2F, 0x4F, 0x2F, 0x5A, 0x39, 0x49, 0x5A, 0x4F, 0x00 }; #define MMS_HDR_BCC 0x01 #define MMS_HDR_CC 0x02 #define MMS_HDR_CONTENT_LOCATION 0x03 #define MMS_HDR_CONTENT_TYPE 0x04 #define MMS_HDR_DATE 0x05 #define MMS_HDR_DELIVERY_REPORT 0x06 #define MMS_HDR_DELIVERY_TIME 0x07 #define MMS_HDR_EXPIRY 0x08 #define MMS_HDR_FROM 0x09 #define MMS_HDR_MESSAGE_CLASS 0x0a #define MMS_HDR_MESSAGE_ID 0x0b #define MMS_HDR_MESSAGE_TYPE 0x0c #define MMS_HDR_MMS_VERSION 0x0d #define MMS_HDR_MESSAGE_SIZE 0x0e #define MMS_HDR_PRIORITY 0x0f #define MMS_HDR_READ_REPLY 0x10 #define MMS_HDR_REPORT_ALLOWED 0x11 #define MMS_HDR_RESPONSE_STATUS 0x12 #define MMS_HDR_RESPONSE_TEXT 0x13 #define MMS_HDR_SENDER_VISIBILITY 0x14 #define MMS_HDR_STATUS 0x15 #define MMS_HDR_SUBJECT 0x16 #define MMS_HDR_TO 0x17 #define MMS_HDR_TRANSACTION_ID 0x18 static const char *mms_header[] = { NULL, "Bcc", "Cc", "X-Mms-Content-Location", "Content-Type", "Date", "X-Mms-Delivery-Report", "X-Mms-Delivery-Time", "X-Mms-Expiry", "From", "X-Mms-Message-Class", "Message-ID", "X-Mms-Message-Type", "X-Mms-MMS-Version", "X-Mms-Message-Size", "X-Mms-Priority", "X-Mms-Read-Reply", "X-Mms-Report-Allowed", "X-Mms-Response-Status", "X-Mms-Response-Text", "X-Mms-Sender-Visibility", "X-Mms-Status", "Subject", "To", "X-Mms-Transaction-Id", }; static const char *wap_header[] = { "Accept", "Accept-Charset (deprecated)", "Accept-Encoding (deprecated)", "Accept-Language", "Accept-Ranges", "Age", "Allow", "Authorization", "Cache-Control (deprecated)", "Connection", "Content-Base (deprecated)", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range (deprecated)", "Content-Type", "Date", "Etag", "Expires", "From", "Host", "If-Modified-Since", "If-Match", "If-None-Match", "If-Range", "If-Unmodified-Since", "Location", "Last-Modified", "Max-Forwards", "Pragma", "Proxy-Authenticate", "Proxy-Authorization", "Public", "Range", "Referer", "Retry-After", "Server", "Transfer-Encoding", "Upgrade", "User-Agent", "Vary", "Via", "Warning", "WWW-Authenticate", "Content-Disposition (deprecated)", "X-Wap-Application-Id", "X-Wap-Content-URI", "X-Wap-Initiator-URI", "Accept-Application", "Bearer-Indication", "Push-Flag", "Profile", "Profile-Diff", "Profile-Warning (deprecated)", "Expect", "TE", "Trailer", "Accept-Charset", "Accept-Encoding", "Cache-Control (deprecated)", "Content-Range", "X-Wap-Tod", "Content-ID", "Set-Cookie", "Cookie", "Encoding-Version", "Profile-Warning", "Content-Disposition", "X-WAP-Security", "Cache-Control", }; static const char *wap_param[] = { "Q", "Charset", "Level", "Type", "Unused", "Name (deprecated)", "Filename (deprecated)", "Differences", "Padding", "Type (multipart)", "Start (deprecated)", "Start-Info (deprecated)", "Comment (deprecated)", "Domain (deprecated)", "Max-Age", "Path (deprecated)", "Secure", "SEC", "MAC", "Creatione-Date", "Modification-Date", "Read-Date", "Size", "Name", "Filename", "Start", "Start-Info", "Comment", "Domain", "Path", }; static void decode_headers (struct wsp_header_iter *iter, const char *header_lut[], const char *prefix) { while (wsp_header_iter_next (iter)) { const unsigned char *hdr = wsp_header_iter_get_hdr (iter); const unsigned char *val = wsp_header_iter_get_val (iter); enum wsp_value_type type = wsp_header_iter_get_val_type (iter); unsigned int len, i; switch (wsp_header_iter_get_hdr_type (iter)) { case WSP_HEADER_TYPE_WELL_KNOWN: printf ("%s%s: ", prefix, header_lut[hdr[0] & 0x7f]); break; case WSP_HEADER_TYPE_APPLICATION: printf ("%s%s: ", prefix, (const char *) hdr); break; default: printf ("Warning: Unhandled case"); break; } len = wsp_header_iter_get_val_len (iter); switch (type) { case WSP_VALUE_TYPE_TEXT: printf ("%s", (const char *) val); break; case WSP_VALUE_TYPE_LONG: case WSP_VALUE_TYPE_SHORT: default: for (i = 0; i < len; i++) printf ("%02x ", val[i]); printf ("(len %d, ", len); if (type == WSP_VALUE_TYPE_SHORT) printf ("Short)"); else printf ("Long)"); break; } printf ("\n"); } } static void decode_parameters (const unsigned char *pdu, unsigned int len, const char *prefix) { struct wsp_parameter_iter iter; struct wsp_parameter param; char buf[128]; printf ("%sParameter Size: %d\n", prefix, len); wsp_parameter_iter_init (&iter, pdu, len); while (wsp_parameter_iter_next (&iter, ¶m)) { if (param.type == WSP_PARAMETER_TYPE_UNTYPED) printf ("%s%s: ", prefix, param.untyped); else printf ("%s%s: ", prefix, wap_param[param.type]); switch (param.value) { case WSP_PARAMETER_VALUE_TEXT: printf ("%s\n", param.text); break; case WSP_PARAMETER_VALUE_INT: printf ("%u\n", param.integer); break; case WSP_PARAMETER_VALUE_DATE: strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (¶m.date)); buf[127] = '\0'; printf ("%s\n", buf); break; case WSP_PARAMETER_VALUE_Q: printf ("\n"); default: printf ("Warning: Unhandled case"); break; } } printf ("\n"); } static void decode_message (const unsigned char *data, unsigned int size) { struct wsp_header_iter iter; struct wsp_multipart_iter mi; unsigned int flags = 0; const void *ct; unsigned int ct_len; const void *multipart_mimetype; unsigned int consumed; flags |= WSP_HEADER_ITER_FLAG_REJECT_CP; flags |= WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART; wsp_header_iter_init (&iter, data, size, flags); decode_headers (&iter, mms_header, ""); if (wsp_header_iter_at_end (&iter) == TRUE) goto done; if (wsp_header_iter_is_multipart (&iter) == FALSE) { printf ("Not a multipart header, but not at the end" "... something is wrong\n"); return; } if (wsp_multipart_iter_init (&mi, &iter, &ct, &ct_len) == FALSE) { printf ("multipart_iter_init failed\n"); return; } if (wsp_decode_content_type (ct, ct_len, &multipart_mimetype, &consumed, NULL) == FALSE) { printf ("Unable to decode multipart-mimetype\n"); return; } printf ("Content-Type: %s\n", (const char *) multipart_mimetype); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpointer-arith" decode_parameters (ct + consumed, ct_len - consumed, "\t"); #pragma GCC diagnostic pop while (wsp_multipart_iter_next (&mi) == TRUE) { struct wsp_header_iter hi; ct = wsp_multipart_iter_get_content_type (&mi); ct_len = wsp_multipart_iter_get_content_type_len (&mi); if (wsp_decode_content_type (ct, ct_len, &multipart_mimetype, &consumed, NULL) == FALSE) { printf ("Unable to decode part multipart-mimetype\n"); return; } printf ("\tContent-Type: %s\n", (const char *) multipart_mimetype); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpointer-arith" decode_parameters (ct + consumed, ct_len - consumed, "\t\t"); #pragma GCC diagnostic pop printf ("\tHeader Size: %d\n", wsp_multipart_iter_get_hdr_len (&mi)); wsp_header_iter_init (&hi, wsp_multipart_iter_get_hdr (&mi), wsp_multipart_iter_get_hdr_len (&mi), 0); decode_headers (&hi, wap_header, "\t"); if (wsp_header_iter_at_end (&hi) == FALSE) { printf ("Unable to fully decode part headers\n"); return; } printf ("\tBody Size: %d\n\n", wsp_multipart_iter_get_body_len (&mi)); } if (wsp_multipart_iter_close (&mi, &iter) == FALSE) { printf ("Unable to close multipart iter\n"); return; } if (wsp_header_iter_at_end (&iter) == FALSE) { printf ("Didn't consume the entire message\n"); return; } done: printf ("Done\n"); } static int open_file (const char *pathname) { struct stat st; unsigned char *map; size_t size; int fd; fd = open (pathname, O_RDONLY); if (fd < 0) return -1; if (fstat (fd, &st) < 0) { close (fd); return -1; } size = st.st_size; map = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (!map || map == MAP_FAILED) { close (fd); return -1; } decode_message (map, size); munmap (map, size); close (fd); return 0; } int main (int argc, char *argv[]) { if (argc < 2) { decode_message (mms_msg1, sizeof(mms_msg1)); return 0; } if (open_file (argv[1]) < 0) { fprintf (stderr, "Failed to open file\n"); return 1; } return 0; } mmsd-2.6.0/tools/meson.build000066400000000000000000000010521456374454100157740ustar00rootroot00000000000000create_hex_array = executable('create-hex-array', 'create-hex-array.c', dependencies : dependencies, include_directories : includes, ) decode_mms = executable('decode-mms', 'decode-mms.c', dependencies : dependencies, include_directories : includes, link_with : [mms_lib] ) if get_option('build-mmsctl') mmsctl = executable('mmsctl', 'mmsctl.c', dependencies : [ dependency('dbus-1', version : '>1'), dependency('json-c', version : '>=0.15'), ], include_directories : includes, install : true, ) endif mmsd-2.6.0/tools/mmsctl.c000066400000000000000000000402501456374454100153000ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2021, Julian P Samaroo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 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 #include #include #include #include #include #include #include #include char *prog; static DBusConnection *conn; static DBusError err; char *services_path; void *messages; // set by command line flags char *target_message_path = NULL; char *target_number = NULL; char **recipients = NULL; int recipients_len = 0; int attachments_len = 0; char **attachments = NULL; char **ctypes = NULL; typedef void (CALLBACK)(DBusMessageIter *); static void send_recv (CALLBACK arg_cb, CALLBACK reply_cb, const char *target, const char *object, const char *interface, const char *method) { DBusMessage *msg; DBusMessageIter args; DBusPendingCall *pending; // create a new method call and check for errors msg = dbus_message_new_method_call (target, object, interface, method); assert (msg && "Failed to allocate new message"); // append arguments dbus_message_iter_init_append (msg, &args); arg_cb (&args); // send message and get a handle for a reply if (!dbus_connection_send_with_reply (conn, msg, &pending, -1)) // -1 is default timeout { fprintf (stderr, "Out Of Memory!\n"); exit (EXIT_FAILURE); } if (pending == NULL) { fprintf (stderr, "Pending Call was NULL\n"); exit (EXIT_FAILURE); } dbus_connection_flush (conn); // free message dbus_message_unref (msg); // block until we recieve a reply dbus_pending_call_block (pending); // get the reply message msg = dbus_pending_call_steal_reply (pending); if (msg == NULL) { fprintf (stderr, "Reply was NULL\n"); exit (EXIT_FAILURE); } // free the pending message handle dbus_pending_call_unref (pending); // read the parameters if (dbus_message_iter_init (msg, &args)) reply_cb (&args); // free reply and close connection dbus_message_unref (msg); } static void get_services_arg_cb (DBusMessageIter *args) { } static void get_services_reply_cb (DBusMessageIter *args) { char *error; DBusMessageIter subsubargs; DBusMessageIter subargs; int arg_type = dbus_message_iter_get_arg_type (args); if (arg_type == DBUS_TYPE_STRING) { dbus_message_iter_get_basic (args, &error); fprintf (stderr, "DBus error: %s\n", error); exit (EXIT_FAILURE); } assert (arg_type == DBUS_TYPE_ARRAY); dbus_message_iter_recurse (args, &subargs); assert (dbus_message_iter_get_arg_type (&subargs) == DBUS_TYPE_STRUCT); dbus_message_iter_recurse (&subargs, &subsubargs); assert (dbus_message_iter_get_arg_type (&subsubargs) == DBUS_TYPE_OBJECT_PATH); dbus_message_iter_get_basic (&subsubargs, &services_path); } static json_object * parse_message_dict_variant_inner (DBusMessageIter *args) { int vartype = dbus_message_iter_get_arg_type (args); if (vartype == DBUS_TYPE_STRING) { char *var_str; dbus_message_iter_get_basic (args, &var_str); return json_object_new_string (var_str); } else if (vartype == DBUS_TYPE_BOOLEAN) { char var; dbus_message_iter_get_basic (args, &var); return json_object_new_boolean (var); } else if (vartype == DBUS_TYPE_ARRAY || vartype == DBUS_TYPE_STRUCT) { DBusMessageIter varelemargs; json_object *subattrs; dbus_message_iter_recurse (args, &varelemargs); subattrs = json_object_new_array (); while (1 == 1) { int elem_type = dbus_message_iter_get_arg_type (&varelemargs); if (elem_type == DBUS_TYPE_STRING) { char *elem_str; dbus_message_iter_get_basic (&varelemargs, &elem_str); json_object_array_add (subattrs, json_object_new_string (elem_str)); } else if (elem_type == DBUS_TYPE_UINT64) { uint64_t elem; dbus_message_iter_get_basic (&varelemargs, &elem); json_object_array_add (subattrs, json_object_new_int (elem)); } else fprintf (stderr, "Unhandled DBus type: %d\n", elem_type); if (!dbus_message_iter_has_next (&varelemargs)) break; dbus_message_iter_next (&varelemargs); } return subattrs; } else { fprintf (stderr, "Unhandled DBus type: %d\n", vartype); exit (EXIT_FAILURE); } } static json_object * parse_message_dict_variant (DBusMessageIter *args) { DBusMessageIter varargs; int vartype; dbus_message_iter_recurse (args, &varargs); vartype = dbus_message_iter_get_arg_type (&varargs); if (vartype == DBUS_TYPE_ARRAY) { DBusMessageIter varargsarray; json_object *subattrs; dbus_message_iter_recurse (&varargs, &varargsarray); subattrs = json_object_new_array (); while (1 == 1) { json_object_array_add (subattrs, parse_message_dict_variant_inner (&varargsarray)); if (!dbus_message_iter_has_next (&varargsarray)) break; dbus_message_iter_next (&varargsarray); } return subattrs; } else return parse_message_dict_variant_inner (&varargs); } static void parse_message (DBusMessageIter *args) { json_object *root = json_object_new_object (); char *message_path; json_object *attrs; assert (dbus_message_iter_get_arg_type (args) == DBUS_TYPE_OBJECT_PATH); dbus_message_iter_get_basic (args, &message_path); if (target_message_path && (strcmp (message_path, target_message_path) != 0)) return; json_object_object_add (root, "message_path", json_object_new_string (message_path)); dbus_message_iter_next (args); attrs = json_object_new_object (); while (1 == 1) // TODO: Unnecessary loop? { DBusMessageIter msgargs; assert (dbus_message_iter_get_arg_type (args) == DBUS_TYPE_ARRAY); dbus_message_iter_recurse (args, &msgargs); while (1 == 1) { char *arg_name; DBusMessageIter msgdictargs; json_object *value; assert (dbus_message_iter_get_arg_type (&msgargs) == DBUS_TYPE_DICT_ENTRY); dbus_message_iter_recurse (&msgargs, &msgdictargs); assert (dbus_message_iter_get_arg_type (&msgdictargs) == DBUS_TYPE_STRING); dbus_message_iter_get_basic (&msgdictargs, &arg_name); dbus_message_iter_next (&msgdictargs); assert (dbus_message_iter_get_arg_type (&msgdictargs) == DBUS_TYPE_VARIANT); value = parse_message_dict_variant (&msgdictargs); json_object_object_add (attrs, arg_name, value); if (!dbus_message_iter_has_next (&msgargs)) break; dbus_message_iter_next (&msgargs); } if (!dbus_message_iter_has_next (args)) break; dbus_message_iter_next (args); } json_object_object_add (root, "attrs", attrs); printf ("%s\n", json_object_to_json_string_ext (root, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_NOSLASHESCAPE)); } _Noreturn static void listen (void) { DBusMessage *msg; DBusMessageIter args; dbus_bus_add_match (conn, "type='signal',interface='org.ofono.mms.Service'", &err); dbus_connection_flush (conn); if (dbus_error_is_set (&err)) { fprintf (stderr, "Match Error (%s)\n", err.message); exit (EXIT_FAILURE); } while (1 == 1) { // non-blocking read of the next available message dbus_connection_read_write (conn, 0); msg = dbus_connection_pop_message (conn); // loop again if we haven't read a message if (!msg) { usleep (100000); continue; } // check if the message is a signal from the correct interface and with the correct name if (dbus_message_is_signal (msg, "org.ofono.mms.Service", "MessageAdded")) { // read the parameters if (!dbus_message_iter_init (msg, &args)) { fprintf (stderr, "Message has no arguments!\n"); goto free; } else if (DBUS_TYPE_OBJECT_PATH != dbus_message_iter_get_arg_type (&args)) { fprintf (stderr, "Argument is not string!\n"); goto free; } else parse_message (&args); } free: dbus_message_unref (msg); } } static void get_messages_arg_cb (DBusMessageIter *args) { } static void get_messages_reply_cb (DBusMessageIter *args) { char *error; DBusMessageIter msg_list; int arg_type = dbus_message_iter_get_arg_type (args); if (arg_type == DBUS_TYPE_STRING) { dbus_message_iter_get_basic (args, &error); fprintf (stderr, "DBus error: %s\n", error); exit (EXIT_FAILURE); } assert (arg_type == DBUS_TYPE_ARRAY); dbus_message_iter_recurse (args, &msg_list); while (dbus_message_iter_get_arg_type (&msg_list) != DBUS_TYPE_INVALID) { DBusMessageIter msg_content; assert (dbus_message_iter_get_arg_type (&msg_list) == DBUS_TYPE_STRUCT); dbus_message_iter_recurse (&msg_list, &msg_content); parse_message (&msg_content); dbus_message_iter_next (&msg_list); } } static void get_messages (void) { send_recv (get_messages_arg_cb, get_messages_reply_cb, "org.ofono.mms", services_path, "org.ofono.mms.Service", "GetMessages"); } static void send_message_arg_cb (DBusMessageIter *args) { DBusMessageIter recipients_args; DBusMessageIter attachments_struct_args; const char *smil = ""; DBusMessageIter variant_args; dbus_message_iter_open_container (args, DBUS_TYPE_ARRAY, "s", &recipients_args); for (int i = 0; i < recipients_len; i++) dbus_message_iter_append_basic (&recipients_args, DBUS_TYPE_STRING, &recipients[i]); dbus_message_iter_close_container (args, &recipients_args); dbus_message_iter_open_container (args, DBUS_TYPE_VARIANT, "s", &variant_args); dbus_message_iter_append_basic (&variant_args, DBUS_TYPE_STRING, &smil); dbus_message_iter_close_container (args, &variant_args); dbus_message_iter_open_container (args, DBUS_TYPE_ARRAY, "(sss)", &attachments_struct_args); for (int i = 0; i < attachments_len; i++) { DBusMessageIter attachments_args; char *cid = malloc (8); // TODO: This segfaults: char cid[8] = {0}; const char *ctype; char *att; dbus_message_iter_open_container (&attachments_struct_args, DBUS_TYPE_STRUCT, NULL, &attachments_args); assert (snprintf (cid, 8, "cid-%d", i)); ctype = ctypes[i]; att = attachments[i]; if (ctype == NULL) { ctype = "text/plain"; /* FIXME: Content Type detection * char file_cmd[255]; * ctype = malloc(255); * freopen("/dev/null", "a", stdout); * setbuf(stdout, ctype); * assert(snprintf(file_cmd, 255, "file -b --mime-type %s", att)); * assert(system(file_cmd) == 0); * freopen("/dev/tty", "a", stdout); */ } dbus_message_iter_append_basic (&attachments_args, DBUS_TYPE_STRING, &cid); dbus_message_iter_append_basic (&attachments_args, DBUS_TYPE_STRING, &ctype); dbus_message_iter_append_basic (&attachments_args, DBUS_TYPE_STRING, &att); dbus_message_iter_close_container (&attachments_struct_args, &attachments_args); } dbus_message_iter_close_container (args, &attachments_struct_args); } static void send_message_reply_cb (DBusMessageIter *args) { } static void send_message (void) { send_recv (send_message_arg_cb, send_message_reply_cb, "org.ofono.mms", services_path, "org.ofono.mms.Service", "SendMessage"); } static void delete_message_arg_cb (DBusMessageIter *args) { } static void delete_message_reply_cb (DBusMessageIter *args) { } static void delete_message (void) { send_recv (delete_message_arg_cb, delete_message_reply_cb, "org.ofono.mms", target_message_path, "org.ofono.mms.Message", "Delete"); } enum mms_command_t { MMS_COMMAND_NONE, MMS_COMMAND_LISTEN, MMS_COMMAND_LIST_MESSAGES, MMS_COMMAND_SEND_MESSAGE, MMS_COMMAND_DELETE_MESSAGE, }; _Noreturn static void usage (void) { fprintf (stderr, "Usage: %s [-L] [-M [-o message_path]] [-S [-r recipient ...] [-a attachment [-c ctype] ...]] [-D -o message_path [-o message_path ...]]\n", prog); exit (EXIT_FAILURE); } static void set_cmd (enum mms_command_t *cmd, enum mms_command_t new_cmd) { if (*cmd != MMS_COMMAND_NONE) { fprintf (stderr, "Conflicting commands!\n"); usage (); } *cmd = new_cmd; } int main (int argc, char *argv[]) { int opt; enum mms_command_t cmd = MMS_COMMAND_NONE; prog = argv[0]; while ((opt = getopt (argc, argv, "LMSDo:r:a:c:")) != -1) switch (opt) { case 'L': set_cmd (&cmd, MMS_COMMAND_LISTEN); break; case 'M': set_cmd (&cmd, MMS_COMMAND_LIST_MESSAGES); break; case 'S': set_cmd (&cmd, MMS_COMMAND_SEND_MESSAGE); break; case 'D': set_cmd (&cmd, MMS_COMMAND_DELETE_MESSAGE); break; case 'o': target_message_path = optarg; break; case 'r': if (cmd != MMS_COMMAND_SEND_MESSAGE) usage (); recipients_len++; if (recipients_len) recipients = realloc (recipients, recipients_len * sizeof(char *)); else recipients = malloc (sizeof(char *)); recipients[recipients_len - 1] = optarg; break; case 'a': if (cmd != MMS_COMMAND_SEND_MESSAGE) usage (); attachments_len++; if (attachments_len == 1) { attachments = malloc (sizeof(char *)); ctypes = malloc (sizeof(char *)); } else { attachments = realloc (attachments, attachments_len * sizeof(char *)); ctypes = realloc (ctypes, attachments_len * sizeof(char *)); } attachments[attachments_len - 1] = optarg; ctypes[attachments_len - 1] = NULL; // Default to auto-detect ctype break; case 'c': if (cmd != MMS_COMMAND_SEND_MESSAGE) usage (); if (ctypes[attachments_len - 1]) { fprintf (stderr, "Only one content type may be specified per attachment!\n"); usage (); } ctypes[attachments_len - 1] = optarg; break; default: usage (); } dbus_error_init (&err); // connect to the session bus conn = dbus_bus_get (DBUS_BUS_SESSION, &err); if (dbus_error_is_set (&err)) { fprintf (stderr, "Connection Error (%s)\n", err.message); dbus_error_free (&err); } if (!conn) { fprintf (stderr, "Connection is NULL\n"); exit (EXIT_FAILURE); } send_recv (get_services_arg_cb, get_services_reply_cb, "org.ofono.mms", "/org/ofono/mms", "org.ofono.mms.Manager", "GetServices"); if (cmd == MMS_COMMAND_LISTEN) listen (); else if (cmd == MMS_COMMAND_LIST_MESSAGES) get_messages (); else if (cmd == MMS_COMMAND_SEND_MESSAGE) { if ((recipients_len == 0) || (attachments_len == 0)) { fprintf (stderr, "-S needs recipients and attachments!\n"); usage (); } send_message (); } else if (cmd == MMS_COMMAND_DELETE_MESSAGE) { if (!target_message_path) { fprintf (stderr, "-D needs message path!\n"); usage (); } delete_message (); } else if (cmd == MMS_COMMAND_NONE) { fprintf (stderr, "No command specified\n"); exit (EXIT_SUCCESS); } else { fprintf (stderr, "Command not implemented!\n"); exit (EXIT_FAILURE); } return 0; } mmsd-2.6.0/uncrustify.cfg000066400000000000000000000077361456374454100154050ustar00rootroot00000000000000# # GNOME style (GNU variant) # input_tab_size = 8 output_tab_size = 8 # { # size = 2; // etc. # ^^ indent_columns = 2 indent_with_tabs = 0 # Don't use tabs for indentation # if ()\n{} // level of endentation before '{' # ^ indent_brace = 2 # exit: #^ // indentation before label # 2 = 1 space indent_label = 2 # size = size + 2; // +, -, *, /, &, |, >>, <<, etc # ^ ^ sp_arith = force # int width, height # ^ ^ sp_after_comma = force sp_before_comma = remove # size = 4; // +=, -=, |=, etc. # ^ ^ sp_assign = add # success = start && go; // &&, || # ^ ^ sp_bool = force # if (size > 2) // >, <, ==, >=, etc. # ^ ^ sp_compare = force # perimeter = (width + height) * 2 // remove space around '(' # ^ ^ sp_inside_paren = remove # int main (void) // remove spaces around '(' in functions # ^ ^ sp_inside_fparen = remove # int main () // remove spaces in empty parameters # ^ sp_inside_fparens = remove # int foo (void); # ^ sp_func_proto_paren = add # int foo (void){} # ^ sp_func_def_paren = force # printf (""); # ^ sp_func_call_paren = force # if ( // if/for/while/switch etc. # ^ sp_before_sparen = force set func_call_user _ N_ C_ Q_ NC_ set func_call_user g_autoptr g_auto # space after user defined functions (above) and paren sp_func_call_user_paren = remove # printf ("") ; # ^ // remove the space sp_before_semi = remove sp_inside_braces = ignore # return (3*2); # ^ sp_return_paren = force # long distance; # int name; # // ^ space b/w type and variable name sp_after_type = add # char *name; // add space before '*' (keep spacing if more than 1) # ^ sp_before_ptr_star = add # char *name; // remove space after '*' # ^ sp_after_ptr_star = remove # char **name (){} // remove space b/w 2 '*' # ^ sp_between_ptr_star = remove # maximum consecutive new lines # 3 = 2 blank lines nl_max = 3 # char *names[] = { // remove newline if any # ^ nl_assign_brace = remove # } else // insert newline # ^ nl_brace_else = force # else if // remove newline if any # ^ nl_else_if = remove # do {} // insert newline # ^ nl_do_brace = force nl_union_brace = force nl_enum_brace = ignore # don't care for enums nl_struct_brace = force # while () {} // insert newline. # ^ nl_while_brace = force nl_for_brace = force nl_if_brace = force nl_switch_brace = force # do ()\n while(); # ^ nl_brace_while = force # int main (void){} // newline after type # ^ nl_func_type_name = force # int main () {} // insert newline # ^ nl_fdef_brace = force # int main () {} # ^ # nl_after_func_body = 2 # int main (int argc, char **argv) // force newline at ',' # ^ nl_func_decl_args = force nl_func_def_args = force # remove blank lines after '{' and before '}' eat_blanks_after_open_brace = true eat_blanks_before_close_brace = true # add newline at end of file nl_end_of_file = force # min number of newlines to be added nl_end_of_file_min = 1 # main (int argc, // add enough spaces to align # char *argv[]) # ^^ align_func_params = true # func (int *square, // stick '*' to parameter # double match) align_var_def_star_style = 2 # #define ADD(x,y,x) ((x) \ # + (y) \ # + (z)) # ^ // align '/' align_nl_cont = true # Add a '*' at beginning of multiline comment lines cmt_star_cont = true # Remove braces for single line statements in # while, for, if, and switch case mod_full_brace_while = remove mod_full_brace_for = remove mod_full_brace_if = remove mod_case_brace = remove # if (is_good \n || \n is_okay) # ^ ^ // If newline present, # where to put &&, ||, | etc. pos_conditional = lead # FIXME: Not working pos_bool = trail # Hack to avoid indentation of following line # consider the following tokens as a comment set COMMENT G_BEGIN_DECLS mmsd-2.6.0/uncrustify.sh000077500000000000000000000031001456374454100152400ustar00rootroot00000000000000#!/bin/sh # Written in 2018 by Mohammed Sadiq # To the extent possible under law, the author(s) have dedicated all # copyright and related and neighboring rights to this software to # the public domain worldwide. This software is distributed without # any warranty. # You should have received a copy of the CC0 Public Domain Dedication # along with this software. If not, see # . if [ ! "$(which uncrustify)" ]; then echo "'uncrustify' Not found. Exiting..." exit 1 fi if [ ! "$(which diff)" ]; then echo "'diff' Not found. Exiting..." exit 1 fi # Handle only .c files. There are unfixable issues uncrustifying .h header files uncrustify -l c -c uncrustify.cfg --no-backup --mtime $(find . -path './src/*' -name '*.c') # Simple Hack to work around issues with handling .h files HEADER_FILES=$(find . -path './src/*' -name '*.h') for file in $HEADER_FILES do # Work on a copy. Replace the file only if some actual change # happened. Otherwise, every header file will have a new mtime # and that will result in compiling every file on rebuild. MTIME=$(stat -c '%Y' "$file") cp "$file" temp.h sed -i "/^G_DECLARE_/ s/)$/);/" temp.h cp temp.h temp.bak uncrustify -l c -c uncrustify.cfg --no-backup temp.h -q echo "Parsing: $file as language C" DIFF="$(diff temp.h temp.bak)" if [ "$DIFF" ]; then sed -i "/^G_DECLARE_/ s/;$//" temp.h mv temp.h "$file" # Reset modification time touch --date=@${MTIME} "$file" fi done # Remove temp files, if any. rm -rf temp.h temp.bak mmsd-2.6.0/unit/000077500000000000000000000000001456374454100134535ustar00rootroot00000000000000mmsd-2.6.0/unit/di-mms-1.mms000066400000000000000000000001201456374454100155060ustar00rootroot00000000000000mavodi-7-88-19-7-9c-60c31484-4695ec471ce+33666565565/TYPE=PLMN`Vmmsd-2.6.0/unit/meson.build000066400000000000000000000024751456374454100156250ustar00rootroot00000000000000test_dependencies = [ dependency('glib-2.0', version : '>=2.16'), dependency('mm-glib', version : '>=1.14'), dependency('libcares', version : '>1.17'), cc.find_library('dl'), cc.find_library('phonenumber', required: true) ] test_mmsutil = executable('test-mmsutil', ['test-mmsutil.c', '../src/mmsutil.c', '../src/wsputil.c'], include_directories : includes, dependencies : test_dependencies ) test_wsputil = executable('test-wsputil', ['test-wsputil.c', '../src/wsputil.c'], include_directories : includes, dependencies : test_dependencies ) test_phone_utils = executable('test_phone_utils', ['test-phone-utils.c', '../src/phone-utils.cpp'], include_directories : includes, dependencies : test_dependencies ) test_service_providers = executable('test_service_providers', ['test-service-providers.c', '../src/service-providers.c'], include_directories : includes, dependencies : test_dependencies ) # tests expect pwd to be at the project root, not in ./unit test('test mms', test_mmsutil, workdir : meson.current_source_dir() + '/..') test('test wsp', test_wsputil, workdir : meson.current_source_dir() + '/..') test('test phone utils', test_phone_utils, workdir : meson.current_source_dir() + '/..') test('test service providers', test_service_providers, workdir : meson.current_source_dir() + '/..') mmsd-2.6.0/unit/ni-mms-1-3-con-271.mms000066400000000000000000000001711456374454100167520ustar00rootroot00000000000000OgQKKB+44123989100/TYPE=PLMN*abcdefghijklmnopqrstuvwxyz0123456789/-+@Z http://eps3.de/O/Z9IZOmmsd-2.6.0/unit/ni-mms-1-3-con-272.mms000066400000000000000000000002521456374454100167530ustar00rootroot00000000000000OgQKKB+44123989100/TYPE=PLMNMMS-1.3-con-272Zhttp://abcdefghi/abcdefghi/abcdefghi/abcdefghi/abcdefghi/abcdefghi/abcdefghi/abcdefghi/abcdefghi.mmsmmsd-2.6.0/unit/rc-mms-1-3-con-210.mms000066400000000000000000000053171456374454100167500ustar00rootroot0000000000000000000000210MMMS-1.3-con-210+33611111111/TYPE=PLMN+33622222222/TYPE=PLMNapplication/smil1application/smil" _%Long_file_name_for_gif_image_60X80_with_non_ASCII_characters_ooo_Length_is_93_characters.gifGIF87a<PTU[閚z)՛-x'RV[_r*΍|||Q (efh,JTx(qG3 0LhV'.֣oopm Ѣ܀زsbab ,<PsH*\XP Ç#&t(E+A0Á>\,Ha)cKq ̨P!A  H@fV,i ΁9Nc`Ƌ?G29vI(H`ɓHun$!H#{"0rVK!&@>0X9%X'S?B@j0Cԫ'FР q$T2( Ơq2/3G0qo,vp\ g&ް; '>.{?]駧j߻2xE$u~?N^;|-_ν U*ϼRJp=@:+Hf Oq'M| Zp@OphOc,΁$'P:h\PqAԠy# HwjP#P:!^ 2ӽ[ahIR?u0x; ../G,@Mq‚7NxAx`h@i,$Hޱэ4HwxbXLNQf}F `>SsA;JP:3r*^n%hX]+Zn 3I)8Aln\eH߱'`$gf]ʘl Ik6Z5ql'@xN.5Ay-|сNd E~4"5Ix.J+aK](Ӱ8ͩNwS\@ P˕ 5GMꔪԢ25BN}*Z*U,=XͪVծv5 ;mmsd-2.6.0/unit/rc-mms-1-3-con-211.mms000066400000000000000000000010401456374454100167360ustar00rootroot0000000000000000000000211M;Shõrt Téxt - ¥üëäÿ+33622222222/TYPE=PLMN+33666565565/TYPE=PLMNapplication/smil+1application/smil"/tmp/php86myoT )"/tmp/php0nDmnYHello Worldmmsd-2.6.0/unit/rc-mms-1-3-con-212.mms000066400000000000000000000003061456374454100167430ustar00rootroot0000000000000000000000212M1MMS-1.3-con-212+33622222222/TYPE=PLMN+33666565565/TYPE=PLMN*;"/tmp/phpnjgFBVThe quick brown fox jumped over the lazy dog 1234567890/!()mmsd-2.6.0/unit/rc-mms-1-3-con-213.mms000066400000000000000000000003711456374454100167460ustar00rootroot0000000000000000000000213MMMS-1.3-con-213+33622222222/TYPE=PLMN+33666565565/TYPE=PLMN'q"/tmp/phpNuduZOFrench ê has a roof over the e. German ü is a u with two dots. Portuguese ã is an a with a ~ (tilde) above.mmsd-2.6.0/unit/rc-mms-1-3-con-214.mms000066400000000000000000000005441456374454100167510ustar00rootroot0000000000000000000000214MlMMS-1.3-con-214+33622222222/TYPE=PLMN+33666565565/TYPE=PLMN*X"/tmp/phpZ198xNFrench has a roof over the e. German is a u with two dots. Portuguese is an a with a ~ (tilde) above.mmsd-2.6.0/unit/rc-mms-1-3-con-216.mms000066400000000000000000000262641456374454100167620ustar00rootroot0000000000000000000000216MܹMMS-1.3-con-216+33622222222/TYPE=PLMN+33666565565/TYPE=PLMNapplication/smil+.application/smil"/tmp/phpBWSFz1 %6"/tmp/phpzfDes9JFIFddDucky< XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmAdobed       x!1AQ"aq2B#Rb3$r!1AQaq"B2Ⴂ# ?Y,2Ą --'ɍ& d7H>#DL!B%?-bx)ySDioHލݭ}sq 6֞* V{]b}I{n[oI .)RN1#K~Yz*_̇%>B:y҆*ܠwqY}_˸ ^Kǂ] hj*Tu;,!R6 +rXFєtHۮ ~,};I$Irzꅲu)e0c 5eg12\U^3PLdVb麹;={$>| rb]q EFG4?HF4Sc?]YlvBFV$Xfi-9zZO80 J[e1-h. S~Œ¶;cW-{飲YAn_gf5S72cy'x}|VK5u {3uvpg$Yn&X;:z[_YUUj}ڝo^Ǽ9^m̼h~l ͩz Lv N, _)nJ2}بk[zLcDjp.hηLS ;Q*5iDHs4y*ox+ZœƵ?;ov+oĊks!h6P5|AXam2+@AYP4=VS}5i=%k+j Іg/1![ ^; $(-ơKKy&'4mʾ"Y;>ޏټe˓%׹X66_6#}oݡSb Ahk+!G1FB $^?ʔ :n=k;˓k*5ҁin| oem<6'?k6P)fH[F:rT9Z̛4tD Hޕ>*Wc~2\Ͷg1yJ2ӋDo_SE.t[Վ:+oY~kZM=5 #qA"*HlݜY9_a*7:.;qrN"jiBKPk+/ 8;v` 2akOX2rG/QA2l]H$=\Q X"B_{71RE%Cj UÓ )ldH˥h v4祱5%!M "H FS ^Lj ,u rK~R1ѧIbkZ )Yo-ҶU{%0'ywBsp >cop( |nu쪥O?P>~߻cebG,h}ji[D#Ycf؁ݡBܗWrl&i鲛w;aw}"+lz;N[4DX$' V^=PuoC*#qR):a#,(8 òo~v`t\4}TJ*mx"Neqm}HyUkb}|W<aAR?Qk[Iǽ!j~r==U~C^`h.RS{3mEDWb@ceq& -Ae^.ŎGk3ȅaZж y";轜J ex#TKsK\XEdT?YMI/ϴn1wow{z4lYb`C Wޙܳq2Pc- -Ucɷ$&[XSg3׏?gNsO4(Aվ;C͉=~ K.%*;9%V?,pƛlkV'Yoto-Z2<BIg0 oR*m@O6bOӜǓQ9/ ŖBMK(5龽[lq%onWFs$II>%ru/nT{eS#ylB>j-<+0VK{fiIpnE OЃ|t#|Z+^I,A< X\D$֛k&ƧA;{)0ql5/QjD]HLt&ZDMD*E1WcQ/'"һ׮nj)d-T̃} ACJ~gMnꮀe_t'] 99+ bP$-yԈ}GִL8FWg _W_2+QA?uS5DpuqQR/r$b v5d0RqIcVltZTEisA:- h}#LbʞP +\Z)VZoOI_?:h0JPG3޸KVy< '.S֪),81!y3#)U|tnTf{{;ʢr+*=M_嶛i )?f[nUq٧6_hZǖS )>V3WpGO&-bˊEU  m ثG&n?n%+Jz7[[ljF}*4#$T~ڮqg3Y&ŀ ky<@'^pi-P5[k$n$r-1>U\-U l"Ŭ~ľʹVRSu*W}NAN$:Fg5`A tkUUVͷ,r oZ&XD(TԂj}FCZ uu-*pV4WbVb}HOIb[UI!뙣V*TbixʒڮVe fIytg2 ai tQ4%'ˬ[l =dqeC~ "]YYJz3tP^".S?w. *1ѓhes!I)R||:v/ftÆ'/tq++%ҎK5'בr|V-v@rkDVpʾ1c5+^;ZRc*u,nu[2{N[ʐM"Ո>`}=A;sa^eR=;`vlM3UʙcIG@F s؏?-\&Fn1I0M#A]"!/yf0)Vhv~Z_[o e# ؚ%gj")2Ux>]>7ʹٿ@ Y\m6E珷c OW4i}0]ߵV!uMeحcS$pnEUfZ)7:WlNz {3).{%J-8PIt#0O Wʰ޻)/Ol|礻4 ȋ{ZFwƦ6[?kdž+g.f#qf(ݑymzL]|kH8szŵČŊ&§iRPgj;x0JWpC#s.oޫZ8 /2#?:oTrb|KBci}4+*$S:ɅdQϕ~SRǓ( &4OtjkˬͫBh[)]ur$3bj$;k!4ÖfHNfyE^w'cOXOX3WSY#sdbŠO@~z_6;eĞu2wqCm9;e'{Y-%Pu*]vlqs`f(+s*<Z>LUd(Y\D) ÕE4oӧNcš|xи1oj;|A-ytlw'[T9ckDLJmmsd-2.6.0/unit/rc-mms-1-3-con-220.mms000066400000000000000000000105541456374454100167500ustar00rootroot0000000000000000000000220M?MMS-1.3-con-220+33622222222/TYPE=PLMN+33666565565/TYPE=PLMNapplication/smil+1application/smil"/tmp/phpLd6N8p (h"/tmp/phpYseMUMGIF87ax蕙uuuRmPV'%$N+DDD\we`'.1 f_3pJr1/rt"Cg{OgXe:]A&N8(z<&-N}sbab ,xH*\ȰÇ#J<(pŋ3jȰ"Ǐ Cqɓ(GLɲK+_LX͛8sɳgMg EX⃎H*]ʴӧ'$]$TA|TX@Қ}Xӷp:cª!zևذ^wU*fVs%4WC^v(Xb[ȨS;nB"V[3ymZOa@ z(_#AHLKvf܈Z 3_ϾG SwvHʻ6@Xz%\cp'_kme'VoiqUh ɭ\q$t >8_)MAheؕVvr^ *e{ 5+Z~WVg3a5P AA̡TeԑHB؀m(waqS %Ef5Uەa`r x|&'#%:`gQIra4*_Q.%Dzj:ZK זur=I 8P;dj\Ԩ,͚(K ,$EEAFiYmFgԃB;p),r)k|Jp %A To Ep8 *'pYLs71 \jCϦܣq]tqIj͗-}+h6tO RD51:+)洮b2~m|'70c'@b)Dh&P.ÁLcdE1_He*{իPrixM1o x*[钃%u6CR3 yTv)f Kd{ݜY9:j0#&#MŲPd>~1gtTR i⺩dn/:RK0t=C8n+$ 䒊tBO~HTDfxLrzxtNW.R.6%HsīUj &l6Yrj <.:_&!2$D#Ȱ UG+O@]j*8 3 [|3${;Vp(ݬ[/tYHVU qdW>Ȑl8ⷷ& gcK]D'r5&wY](4mGD KB82=BwBӎ B;$Տ .W`ʊg'$[R!%{S{_*aMО3< @U1>pAֳtGfuO=%0H*/sw+NoGeZ6WbF"҉M܁%; .a =Ɉ2QU+L(ؙ7iCkѦ?Zr#VU9xѦI<;Ϋ%Uu[b2AJhZVuVkN0 H0lbv ^3ήQڛvnmrNwl͸p[>uj@z޷& !ye>$W|w;4wM厵5nVG3]0q T\@:e8 PԧF‰pn]C/ѓ`gᒀַ~^vdv8tC:q3D)8ˍoA5KxVD'tAF;.4~Wz(W[r3/o{o킠))O왿ftk??s &;WOt{O>8t}~z'yg~1vhTu$gFu wgy~3#0gC.Hh.4,d{3yz} w%#AHw9HG&)+@~I=L؄vozGCx0ZȅI8<_2w=X/7h#|mQq 0 }dggxV}+8I0`P胉ȗD舏G82dXvHj (d40XxHhV2@"(A( @ȇ")҈ĸ}wȈ؊"0xQdHʸ؍E Ȍ" x& Ɨݸ"`z،Ȑ YHz،X؏9%ؘɍ}y#%Qgڨɒ)/؉59F JH9rH+GhwDsO 4و7 [I}L` QX*f )[ Fmc1蘒IDwxiV{ e18XYiٖ1@99il`79"R-0ə鏞+Yy YA-V`9YYiGi FјٝZɗvٕ뵞 oٔ^Ian)ZyJ Z;mmsd-2.6.0/unit/rc-mms-1-3-con-224.mms000066400000000000000000000102511456374454100167460ustar00rootroot0000000000000000000000224M㸖MMS-1.3-con-224+33622222222/TYPE=PLMN+33666565565/TYPE=PLMNapplication/smil+:application/smil"/tmp/phpfIU5gx 1"/tmp/phpSgZlHzGIF89ax%$3pJe:]f_DDDOgXCg{babuuusw/'+8(<ARme`1r\}׽&-rt '.PV&Nz"NN 1蕙! NETSCAPE2.0! ?,xmH,Ȥrl:Ш(Zجvˬr8q9n<~/]sH2,.( Kqf8>^5;>=8D8=>;k;C;O2+ Ja5>EBH pgO (pSae 9p1 LD\.-p2Ukm{g I67, |MQLM@/#ѲaLa}xAkP{û L냭?iE56" 5vЮ2:x+h6ۣVhx?"r 7,LMs<ӰK%…*ٲRfϬrCK2-P-jV2dx>4n04rby>`GCN׌= N-ݱߓFͷ7c߳bCɏ"=yV2"Yx/V 찖}OiQ ܧAp/@3_FyR'pYT7ZK3*ʇ`9B_"?,yA@nh]׿Xx.$" !Y{8Y*pB+*8"kzԺ$i5yVT2w9\IsK GD።3#Ejw튟%ѯ)ip4`WdKj,@/HdE&,I3*90kԣoibN^S[f6 eVD%gYrm-GNf1y,z@ݞ"<~^ [U*bSʂkp0̜(ENT$BCˡ9N*cK!Դ.*y|rps7=M@a c y7L2˃~M[L" A_N{Z&,! G.j;+E b+wrG<27MBu7s|YRFH`cq}/xwo?7|#{˶:yxxWG/߹# tc:`PVB O謹~_=/?uG~D`B<7߀+UOGmOU{O{| i q H 8 H gyoGx(~W HC &x'G~ׁ,(^p{{Fh -O"vTgᇁ `w.x38׶ĉS1zjx8 `hs䤊X@)HvP%x(8X{h1^ȆØX3PΈfp'hȍJ\(`HhS)¨(xZC 5yX` IK8;n9a09ȈȒhSP:ٓ<ٓ8@ɒB9YFɏIH! ?,+ Fm8E1,6O%r9% Trm뽆c-;$Vpz}kQlg~dK\FA! ?,$ ?ߏFj;_7$>߮98_ :5>LCl#R :V믛m?IQA! ?,+ Jm8q=߮f$:8o1bwᲴfƄZhܡa4+?%]S5NXI8H^`OlUxdoA! ?,2 CߏF$ ;NXlq>%6 {Zz5 ڪ[T.uWhjFocVoA! ?,: IG+ kdrH,҄8Tt>QzMp-WpBz&:E$ p8Q=Zk?oR=~ZZnCA;mmsd-2.6.0/unit/rc-mms-1-3-con-228.mms000066400000000000000000000055511456374454100167610ustar00rootroot0000000000000000000000228M–MMS-1.3-con-228+33622222222/TYPE=PLMN+33666565565/TYPE=PLMNapplication/smil+1application/smil"/tmp/phpu66A06 (e"/tmp/phpRWzGLA xUUUUUUUUUUUUUUUUUUUmmmmmmmUWUUUUUUU[}m0`m}}x`h`{uЧ&8W u.jeݫZT82`5 }lfdoo[l ?= o} $_ >bu]0`z귿߿k_{_z۷_?{o_??u~zۮ]ܽ+~?[owk\{kwx{n WhۤUJ%*"{%)UUU*|<*UU@ ߫7UWJ?zoꪫUT }߿ڕTTCv-Wu߿UWUUCzހjC }߽oʪRvzUTUUC @ԕ_ nU_TX#ߪRC1uj }#ʥ<o8[UU?UUC< ]^iԪJxWuv+1application/smil"/tmp/phppFdHSM )"/tmp/phpKw4oMIHello Worldmmsd-2.6.0/unit/rc-mms-1-3-con-281.mms000066400000000000000000000141521456374454100167550ustar00rootroot0000000000000000000000281MMMS-1.3-con-281+33622222222/TYPE=PLMN+33666565565/TYPE=PLMNX-MMS-Unrecognised-Header-FieldYes)"/tmp/phpDhBvLeHello World#"/tmp/phpoez0I8JFIFddDucky< XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmAdobed       <P!1AQa"q2B#bR3!1AQaq2"BR# ?g7yX‘Q&A+5XoΊׅ m Gp`xRO?C)ic$/6cmӘZG6(OQ{=2SO(|-䘖I.*vIAmi˓d}0X gUX3zfT픵G\>B/qcq 5*&T9HI*oe`xo@U<0BDLf2gX1 anZ2})HHCtKD]!֝uK,vPm:ҘTv 󓛕WE {,Q52_Z&EƩW=ʹi{,]v+CtgMSeN=?pD>>iSҺ_hsLuKo>gnX_ܓ%--c2xu;# {|5ջGH%*wE TMZh}@mtgm2ظ6RZ0+/V(yi%WŌR}q97%&Z\{wSDKHOA[M(yF2ctx6!i½u'3p, /\gd5 1MIT~@̯hd_O8\&k{wl#sAr k2 Ϟ S*&FqHT*.$p6 ;H?! k-qh1֤:8I;BNuP}o鷟#;[)岋碤0\E_==)]L,;AEݔ84H*IJ,y_ezQ`+_}ǹ[tK,A6ؘJ;׆ lrWNT'5o9(m̒O-8n]UqjGDf JY|;ⶺ(3>h":A[cqw^9.>9DC,.B ۳Sj:@ve.dѦo9@A`~CE.ɭńbOcf$ꍠrdYCx,绷kPJpn&b0~6t+,|@c9dm z|uUp}RL8grm2*>jyjӅUw6BTyf,?ldKBĖ6 !EI77-MCQʧtv^b$ҢnPIP rh:[D=㹻ko1Asr9`C'd7T|R=/9)fuSec1XI=olɜV"N١Ad2åw0#">gƻA$aO.l:qVHwjsIv<7 D"pV$ \cᘇU2H9K15uG0m1NaJIH 5j9e(FC,a9GDa#inK|$yBP#)@45kf!K=]`_NY,h+])ynY{*3/DuˡC\(=ۗoN{Ÿ=v"sT7RwC]i]`m}[_Sqy?M^ϳُ4)]wh(vdn>ӻZV~޻wSOCv>䞅Dn5k^fޤY 6æmmsd-2.6.0/unit/rc-mms-1-3-con-282.mms000066400000000000000000000141271456374454100167600ustar00rootroot0000000000000000000000282MÖMMS-1.3-con-282+33622222222/TYPE=PLMN+33666565565/TYPE=PLMNNewMessageClass)"/tmp/phpSYH61uHello World#"/tmp/phprzSGZSJFIFddDucky< XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmAdobed       <P!1AQa"q2B#bR3!1AQaq2"BR# ?g7yX‘Q&A+5XoΊׅ m Gp`xRO?C)ic$/6cmӘZG6(OQ{=2SO(|-䘖I.*vIAmi˓d}0X gUX3zfT픵G\>B/qcq 5*&T9HI*oe`xo@U<0BDLf2gX1 anZ2})HHCtKD]!֝uK,vPm:ҘTv 󓛕WE {,Q52_Z&EƩW=ʹi{,]v+CtgMSeN=?pD>>iSҺ_hsLuKo>gnX_ܓ%--c2xu;# {|5ջGH%*wE TMZh}@mtgm2ظ6RZ0+/V(yi%WŌR}q97%&Z\{wSDKHOA[M(yF2ctx6!i½u'3p, /\gd5 1MIT~@̯hd_O8\&k{wl#sAr k2 Ϟ S*&FqHT*.$p6 ;H?! k-qh1֤:8I;BNuP}o鷟#;[)岋碤0\E_==)]L,;AEݔ84H*IJ,y_ezQ`+_}ǹ[tK,A6ؘJ;׆ lrWNT'5o9(m̒O-8n]UqjGDf JY|;ⶺ(3>h":A[cqw^9.>9DC,.B ۳Sj:@ve.dѦo9@A`~CE.ɭńbOcf$ꍠrdYCx,绷kPJpn&b0~6t+,|@c9dm z|uUp}RL8grm2*>jyjӅUw6BTyf,?ldKBĖ6 !EI77-MCQʧtv^b$ҢnPIP rh:[D=㹻ko1Asr9`C'd7T|R=/9)fuSec1XI=olɜV"N١Ad2åw0#">gƻA$aO.l:qVHwjsIv<7 D"pV$ \cᘇU2H9K15uG0m1NaJIH 5j9e(FC,a9GDa#inK|$yBP#)@45kf!K=]`_NY,h+])ynY{*3/DuˡC\(=ۗoN{Ÿ=v"sT7RwC]i]`m}[_Sqy?M^ϳُ4)]wh(vdn>ӻZV~޻wSOCv>䞅Dn5k^fޤY 6æmmsd-2.6.0/unit/sr-mms-mixed.mms000066400000000000000000000001321456374454100165070ustar00rootroot00000000000000000000000001+33612345678/TYPE=PLMN"Hello World mmsd-2.6.0/unit/sr-mms-related-multi-to.mms000066400000000000000000000007341456374454100206010ustar00rootroot00000000000000000000000002+33612345678/TYPE=PLMN+33612345679/TYPE=PLMNapplication/smil1application/smil" "Hello World mmsd-2.6.0/unit/test-mmsutil.c000066400000000000000000001035361456374454100162760ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include #include #include #include #include #include #include "mmsutil.h" static const char * message_type_to_string (enum mms_message_type type) { switch (type) { case MMS_MESSAGE_TYPE_SEND_REQ: return "send-req"; case MMS_MESSAGE_TYPE_SEND_CONF: return "send-conf"; case MMS_MESSAGE_TYPE_NOTIFICATION_IND: return "notification-ind"; case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: return "notifyresp-ind"; case MMS_MESSAGE_TYPE_RETRIEVE_CONF: return "retrieve-conf"; case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: return "acknowledge-ind"; case MMS_MESSAGE_TYPE_DELIVERY_IND: return "delivery-ind"; default: return ""; } return NULL; } static void dump_notification_ind (struct mms_message *msg) { char buf[128]; strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (&msg->ni.expiry)); buf[127] = '\0'; g_print ("From: %s\n", msg->ni.from); g_print ("Subject: %s\n", msg->ni.subject); g_print ("Class: %s\n", msg->ni.cls); g_print ("Size: %d\n", msg->ni.size); g_print ("Expiry: %s\n", buf); g_print ("Location: %s\n", msg->ni.location); } static void dump_attachment (gpointer data, gpointer user_data) { struct mms_attachment *attach = data; g_print ("Attachment:\n"); g_print ("\tOffset: %zd\n", attach->offset); g_print ("\tLength: %zd\n", attach->length); g_print ("\tContent-type: %s\n", attach->content_type); g_print ("\tContent-id: %s\n", attach->content_id); } static void dump_retrieve_conf (struct mms_message *msg) { char buf[128]; strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (&msg->rc.date)); buf[127] = '\0'; g_print ("From: %s\n", msg->rc.from); g_print ("To: %s\n", msg->rc.to); g_print ("Subject: %s\n", msg->rc.subject); g_print ("Class: %s\n", msg->rc.cls); g_print ("Priority: %s\n", msg->rc.priority); g_print ("Msg-Id: %s\n", msg->rc.msgid); g_print ("Date: %s\n", buf); g_slist_foreach (msg->attachments, dump_attachment, NULL); } static void dump_send_conf (struct mms_message *msg) { g_print ("Response-Status: %s\n", message_rsp_status_to_string (msg->sc.rsp_status)); g_print ("Msg-Id: %s\n", msg->sc.msgid); } static void dump_send_req (struct mms_message *msg) { g_print ("To: %s\n", msg->sr.to); g_slist_foreach (msg->attachments, dump_attachment, NULL); } static void dump_delivery_ind (struct mms_message *msg) { char buf[128]; strftime (buf, 127, "%Y-%m-%dT%H:%M:%S%z", localtime (&msg->di.date)); buf[127] = '\0'; g_print ("MMS version: %u.%u\n", (msg->version & 0x70) >> 4, msg->version & 0x0f); g_print ("Msg ID: %s\n", msg->di.msgid); g_print ("To: %s\n", msg->di.to); g_print ("Date: %s\n", buf); g_print ("Delivery Report status: %d\n", msg->di.dr_status); } static gboolean check_encoded_msg (const char *filename, const unsigned char *msg_pdu) { struct stat st; unsigned char *pdu; int fd; int i; int ret; fd = open (filename, O_RDONLY); if (fd < 0) { g_printerr ("Failed to open %s\n", filename); return FALSE; } if (fstat (fd, &st) < 0) { g_printerr ("Failed to stat %s\n", filename); close (fd); return FALSE; } pdu = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (!pdu || pdu == MAP_FAILED) { g_printerr ("Failed to mmap %s\n", filename); close (fd); return FALSE; } if (g_test_verbose ()) { for (i = 0; i < st.st_size; i++) g_print ("%02x ", pdu[i]); g_print ("\n"); } ret = memcmp (msg_pdu, pdu, st.st_size); munmap (pdu, st.st_size); close (fd); return ret == 0; } /* * MMS M-Notify.Ind PDU 1 * This PDU shows the decoding of a M-Notify.Ind PDU with below content: * Overall message size: 68 * MMS message type: notification-ind * MMS transaction id: OgQKKB * MMS version: 1.0 * From: Erotik * Subject: Pin-Ups * Class: Personal * Size: 16384 * Expiry: 2011-05-19T10:56:340200 * Location: http://eps3.de/O/Z9IZO */ static const unsigned char mms_m_notify_ind_1[] = { 0x8C, 0x82, 0x98, 0x4F, 0x67, 0x51, 0x4B, 0x4B, 0x42, 0x00, 0x8D, 0x90, 0x89, 0x08, 0x80, 0x45, 0x72, 0x6F, 0x74, 0x69, 0x6B, 0x00, 0x96, 0x50, 0x69, 0x6E, 0x2D, 0x55, 0x70, 0x73, 0x00, 0x8A, 0x80, 0x8E, 0x02, 0x40, 0x00, 0x88, 0x05, 0x81, 0x03, 0x03, 0xF4, 0x80, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x65, 0x70, 0x73, 0x33, 0x2E, 0x64, 0x65, 0x2F, 0x4F, 0x2F, 0x5A, 0x39, 0x49, 0x5A, 0x4F, 0x00 }; /* * MMS M-Notify.Ind PDU 2 * This PDU shows the decoding of a M-Notify.Ind PDU without "subject" field * and below content: * Overall message size: 93 * MMS message type: notification-ind * MMS transaction id: wLJeT7THu * MMS version: 1.0 * From: 15551230000/TYPE=PLMN * Subject: (null) * Class: Personal * Size: 23069 * Expiry: 2011-05-19T14:32:320200 * Location: http://mmsc11:10021/mmsc/1_1?wLJeT7THu */ static const unsigned char mms_m_notify_ind_2[] = { 0x8C, 0x82, 0x98, 0x77, 0x4C, 0x4A, 0x65, 0x54, 0x37, 0x54, 0x48, 0x75, 0x00, 0x8D, 0x90, 0x89, 0x17, 0x80, 0x31, 0x35, 0x35, 0x35, 0x31, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x8A, 0x80, 0x8E, 0x02, 0x5A, 0x1D, 0x88, 0x05, 0x81, 0x03, 0x03, 0xF4, 0x80, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6D, 0x6D, 0x73, 0x63, 0x31, 0x31, 0x3A, 0x31, 0x30, 0x30, 0x32, 0x31, 0x2F, 0x6D, 0x6D, 0x73, 0x63, 0x2F, 0x31, 0x5F, 0x31, 0x3F, 0x77, 0x4C, 0x4A, 0x65, 0x54, 0x37, 0x54, 0x48, 0x75, 0x00 }; /* * MMS M-Notify.Ind PDU 3 * MMS-1.3-con-271: Long Subject field. * This PDU shows the decoding of a M-Notify.Ind PDU with a maximum length * "us-ascii" encoded "subject" field value (40 characters) and below content: * Overall message size: 121 * MMS message type: notification-ind * MMS transaction id: OgQKKB * MMS version: 1.0 * From: +44123989100/TYPE=PLMN * Subject: abcdefghijklmnopqrstuvwxyz0123456789/-+@ * Class: Personal * Size: 23069 * Expiry: 2011-05-27T10:39:58+0200 * Location: http://eps3.de/O/Z9IZO */ static const char mms_m_notify_ind_3[] = "./unit/ni-mms-1-3-con-271.mms"; /* * MMS M-Notify.Ind PDU 4 * MMS-1.3-con-272: Long X-Mms-Content-Location field in Notification. * This PDU shows the decoding of a M-Notify.Ind PDU with a maximum length * "X-Mms-Content-Location field value (100 characters) and below content: * Overall message size: 170 * MMS message type: notification-ind * MMS transaction id: OgQKKB * MMS version: 1.0 * From: +44123989100/TYPE=PLMN * Subject: MMS-1.3-con-272 * Class: Personal * Size: 23069 * Expiry: 2011-05-27T10:39:58+0200 * Location: http://abcdefghi/abcdefghi/abcdefghi/abcdefghi/abcdefghi/abcdefghi * /abcdefghi/abcdefghi/abcdefghi.mms */ static const char mms_m_notify_ind_4[] = "./unit/ni-mms-1-3-con-272.mms"; /* * MMS M-Retrieve.Conf PDU 1 * This PDU shows the decoding of a M-Retrieve.Conf PDU with an "us-ascii" * encoded text "subject" field and below content: * Overall message size: 200 * MMS message type: retrieve-conf * MMS transaction id: 1201657238 * MMS version: 1.3 * From: 49891000/TYPE=PLMN * To: (null) * Subject: MMS-1.3-con-212 * Class: (null) * Priority: (null) * Msg-Id: mt-212 * Date: 2008-01-30T02:40:380100 */ static const unsigned char mms_m_retrieve_conf_1[] = { 0x8C, 0x84, 0x98, 0x31, 0x32, 0x30, 0x31, 0x36, 0x35, 0x37, 0x32, 0x33, 0x38, 0x00, 0x8D, 0x93, 0x8B, 0x6D, 0x74, 0x2D, 0x32, 0x31, 0x32, 0x00, 0x85, 0x04, 0x47, 0x9F, 0xD5, 0x96, 0x89, 0x15, 0x80, 0x2B, 0x34, 0x39, 0x38, 0x39, 0x31, 0x30, 0x30, 0x30, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x96, 0x11, 0x83, 0x4D, 0x4D, 0x53, 0x2D, 0x31, 0x2E, 0x33, 0x2D, 0x63, 0x6F, 0x6E, 0x2D, 0x32, 0x31, 0x32, 0x00, 0x84, 0xA3, 0x01, 0x40, 0x3B, 0x14, 0x83, 0x85, 0x54, 0x65, 0x78, 0x74, 0x5F, 0x75, 0x73, 0x2D, 0x61, 0x73, 0x63, 0x69, 0x69, 0x2E, 0x74, 0x78, 0x74, 0x00, 0x81, 0x83, 0xC0, 0x22, 0x3C, 0x54, 0x65, 0x78, 0x74, 0x5F, 0x75, 0x73, 0x2D, 0x61, 0x73, 0x63, 0x69, 0x69, 0x2E, 0x74, 0x78, 0x74, 0x3E, 0x00, 0x8E, 0x54, 0x65, 0x78, 0x74, 0x5F, 0x75, 0x73, 0x2D, 0x61, 0x73, 0x63, 0x69, 0x69, 0x2E, 0x74, 0x78, 0x74, 0x00, 0x54, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6B, 0x20, 0x62, 0x72, 0x6F, 0x77, 0x6E, 0x20, 0x66, 0x6F, 0x78, 0x20, 0x6A, 0x75, 0x6D, 0x70, 0x65, 0x64, 0x20, 0x6F, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x7A, 0x79, 0x20, 0x64, 0x6F, 0x67, 0x20, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2F, 0x21, 0x28, 0x29, }; /* * MMS M-Retrieve.Conf PDU 2 * This PDU shows the decoding of a M-Retrieve.Conf PDU with multiple "To" * fields and below content: * Overall message size: 192 * MMS message type: retrieve-conf * MMS transaction id: 1201657238 * MMS version: 1.0 * From: 1234567890/TYPE=PLMN * To: 1111111111/TYPE=PLMN,2222222222/TYPE=PLMN,3333333333/TYPE=PLMN * Subject: multito * Class: Personal * Priority: Normal * Msg-Id: (null) * Date: 2011-04-04T11:41:500200 */ static const unsigned char mms_m_retrieve_conf_2[] = { 0x8C, 0x84, 0x98, 0x31, 0x32, 0x30, 0x31, 0x36, 0x35, 0x37, 0x32, 0x33, 0x38, 0x00, 0x8D, 0x90, 0x85, 0x04, 0x4D, 0x99, 0x92, 0x5E, 0x96, 0x6D, 0x75, 0x6C, 0x74, 0x69, 0x74, 0x6F, 0x00, 0x89, 0x17, 0x80, 0x2B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x97, 0x2B, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x97, 0x2B, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x97, 0x2B, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x8A, 0x80, 0x8F, 0x81, 0x94, 0x80, 0x86, 0x81, 0x90, 0x81, 0x84, 0xA3, 0x01, 0x26, 0x0E, 0x83, 0xC0, 0x22, 0x3C, 0x47, 0x65, 0x6E, 0x65, 0x72, 0x69, 0x63, 0x5F, 0x54, 0x65, 0x78, 0x74, 0x2E, 0x74, 0x78, 0x74, 0x3E, 0x00, 0x8E, 0x2F, 0x74, 0x6D, 0x70, 0x2F, 0x70, 0x68, 0x70, 0x38, 0x76, 0x6C, 0x66, 0x79, 0x42, 0x00, 0xEF, 0xBB, 0xBF, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64 }; /* * MMS M-Retrieve.Conf PDU 3 * Overall message size: 147 * MMS message type: retrieve-conf * MMS transaction id: (null) * MMS version: 1.0 * From: 1234567890/TYPE=PLMN * To: 6666666666/TYPE=PLMN * Subject: test without transaction ID * Class: Personal * Priority: Normal * Msg-Id: (null) * Date: 2011-04-08T12:27:050200 */ static const unsigned char mms_m_retrieve_conf_3[] = { 0x8C, 0x84, 0x8D, 0x90, 0x85, 0x04, 0x4D, 0x9E, 0xE2, 0xF9, 0x96, 0x74, 0x65, 0x73, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6F, 0x75, 0x74, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x49, 0x44, 0x00, 0x89, 0x17, 0x80, 0x2B, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x97, 0x2B, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x8A, 0x80, 0x8F, 0x81, 0x94, 0x80, 0x86, 0x81, 0x90, 0x81, 0x84, 0xA3, 0x01, 0x1F, 0x0E, 0x83, 0xC0, 0x22, 0x3C, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x2E, 0x74, 0x78, 0x74, 0x3E, 0x00, 0x8E, 0x2F, 0x74, 0x6D, 0x70, 0x2F, 0x70, 0x68, 0x70, 0x32, 0x49, 0x6E, 0x74, 0x53, 0x37, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x20, 0x21, 0x0A }; /* * MMS M-Retrieve.Conf PDU 4 * MMS-1.3-con-210: Long Media Content-Location header field value. * This PDU shows the decoding of a M-Retrieve.Conf PDU where a SMIL part * references an object using a long "Content-Location" field value. * Overall message size: 2767 * MMS message type: retrieve-conf * MMS transaction id: 00000000210 * MMS version: 1.3 * From: +33611111111/TYPE=PLMN * To: +33622222222/TYPE=PLMN * Subject: MMS-1.3-con-210 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-05-31T10:42:30+0200 */ static const char mms_m_retrieve_conf_4[] = "./unit/rc-mms-1-3-con-210.mms"; /* * MMS M-Retrieve.Conf PDU 5 * MMS-1.3-con-271: Long Subject field. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a maximum length * "us-ascii" encoded "subject" field value (40 characters) and below content: * Overall message size: 556 * MMS message type: retrieve-conf * MMS transaction id: 00000000271 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: abcdefghijklmnopqrstuvwxyz0123456789/-+@ * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T11:23:30+0200 */ static const char mms_m_retrieve_conf_5[] = "./unit/rc-mms-1-3-con-271.mms"; /* * MMS M-Retrieve.Conf PDU 6 * MMS-1.3-con-212: Text with US-ASCII encoding. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a text object * with "us-ascii" encoding and below content: * Overall message size: 198 * MMS message type: retrieve-conf * MMS transaction id: 00000000212 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-212 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T11:45:21+0200 */ static const char mms_m_retrieve_conf_6[] = "./unit/rc-mms-1-3-con-212.mms"; /* * MMS M-Retrieve.Conf PDU 7 * MMS-1.3-con-213: Text with UTF-8 encoding. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a text object * with "utf-8" encoding and below content: * Overall message size: 249 * MMS message type: retrieve-conf * MMS transaction id: 00000000213 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-213 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T11:52:45+0200 */ static const char mms_m_retrieve_conf_7[] = "./unit/rc-mms-1-3-con-213.mms"; /* * MMS M-Retrieve.Conf PDU 8 * MMS-1.3-con-214: Text with UTF-16 encoding. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a text object * with "utf-16" encoding and below content: * Overall message size: 356 * MMS message type: retrieve-conf * MMS transaction id: 00000000214 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-214 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T14:41:16+0200 */ static const char mms_m_retrieve_conf_8[] = "./unit/rc-mms-1-3-con-214.mms"; /* * MMS M-Retrieve.Conf PDU 9 * MMS-1.3-con-216: JPG Image size 160x120. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a 160x120 JPG * Image object and below content: * Overall message size: 114440 * MMS message type: retrieve-conf * MMS transaction id: 00000000216 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-216 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T15:08:09+0200 */ static const char mms_m_retrieve_conf_9[] = "./unit/rc-mms-1-3-con-216.mms"; /* * MMS M-Retrieve.Conf PDU 10 * MMS-1.3-con-220: GIF Image size 160x120. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a 160x120 GIF87a * Image object and below content: * Overall message size: 4460 * MMS message type: retrieve-conf * MMS transaction id: 00000000220 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-220 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T15:27:27+0200 */ static const char mms_m_retrieve_conf_10[] = "./unit/rc-mms-1-3-con-220.mms"; /* * MMS M-Retrieve.Conf PDU 11 * MMS-1.3-con-224: Animated GIF Image size 160x120. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a 160x120 animated * GIF87a Image object and below content: * Overall message size: 4265 * MMS message type: retrieve-conf * MMS transaction id: 00000000224 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-224 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T15:38:00+0200 */ static const char mms_m_retrieve_conf_11[] = "./unit/rc-mms-1-3-con-224.mms"; /* * MMS M-Retrieve.Conf PDU 12 * MMS-1.3-con-228: WBMP Image size 160x120. * This PDU shows the decoding of a M-Retrieve.Conf PDU with a 160x120 WBMP * Image object and below content: * Overall message size: 2921 * MMS message type: retrieve-conf * MMS transaction id: 00000000228 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-228 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T15:46:42+020 */ static const char mms_m_retrieve_conf_12[] = "./unit/rc-mms-1-3-con-228.mms"; /* * MMS M-Retrieve.Conf PDU 13 * MMS-1.3-con-211: Subject field with UTF8 encoding. * This PDU shows the decoding of a M-Retrieve.Conf PDU with Subject field * with UTF8 encoding and below content: * Overall message size: 544 * MMS message type: retrieve-conf * MMS transaction id: 00000000211 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: Shõrt Téxt - ¥üëäÿ * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T16:01:31+0200 */ static const char mms_m_retrieve_conf_13[] = "./unit/rc-mms-1-3-con-211.mms"; /* * MMS M-Retrieve.Conf PDU 14 * MMS-1.3-con-282: Receive recognised fields with unrecognised values. * This PDU shows the decoding of a M-Retrieve.Conf PDU with recognised field * but with an unrecognised value (X-Mms-Message-Class: "NewMessageClass") and * below content: * Overall message size: 6231 * MMS message type: retrieve-conf * MMS transaction id: 00000000282 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-282 * Class: NewMessageClass * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T16:25:07+0200 */ static const char mms_m_retrieve_conf_14[] = "./unit/rc-mms-1-3-con-282.mms"; /* * MMS M-Retrieve.Conf PDU 15 * MMS-1.3-con-281: Receive unrecognised header field. * This PDU shows the decoding of a M-Retrieve.Conf PDU with an unrecognised * header field (X-MMS-Unrecognised-Header-Field: "Yes") and below content: * Overall message size: 6250 * MMS message type: retrieve-conf * MMS transaction id: 00000000281 * MMS version: 1.3 * From: +33622222222/TYPE=PLMN * To: +33666565565/TYPE=PLMN * Subject: MMS-1.3-con-281 * Class: (null) * Priority: (null) * Msg-Id: (null) * Date: 2011-06-03T16:37:02+0200 */ static const char mms_m_retrieve_conf_15[] = "./unit/rc-mms-1-3-con-281.mms"; /* * MMS M-Send.Req PDU 1 * This PDU shows the decoding of a M-Send.Req PDU with a content-type * value "application/vnd.wap.multipart.mixed" and below content: * Overall message size: 90 * MMS message type: send-req * MMS transaction id: 000000000001 * MMS version: 1.0 * To: +33612345678/TYPE=PLMN * Attachment: * Offset: 75 * Length: 15 * Content-type: text/plain;charset=utf-8 * Content-id: */ static const char mms_m_send_req_1[] = "./unit/sr-mms-mixed.mms"; /* * MMS M-Send.Req PDU 2 * This PDU shows the decoding of a M-Send.Req PDU with a content-type * value "application/vnd.wap.multipart.related" and multiple TO fields. * Overall message size: 476 * MMS message type: send-req * MMS transaction id: 000000000002 * MMS version: 1.0 * To: +33612345678/TYPE=PLMN,+33612345679/TYPE=PLMN * Attachment: * Offset: 129 * Length: 305 * Content-type: application/smil * Content-id: * Attachment: * Offset: 461 * Length: 15 * Content-type: text/plain;charset=utf-8 * Content-id: */ static const char mms_m_send_req_2[] = "./unit/sr-mms-related-multi-to.mms"; /* * MMS M-Send.Conf PDU 1 * This PDU shows the decoding of a M-Send.Conf PDU with an accepted "response * status" and a below content: * Overall message size: 28 * MMS message type: send-conf * MMS transaction id: 31887 * MMS version: 1.0 * Response-Status: ok * Msg-Id: 4dc268d71438a */ static const unsigned char mms_m_send_conf_1[] = { 0x8C, 0x81, 0x98, 0x33, 0x31, 0x38, 0x38, 0x37, 0x00, 0x8D, 0x90, 0x92, 0x80, 0x8B, 0x34, 0x64, 0x63, 0x32, 0x36, 0x38, 0x64, 0x37, 0x31, 0x34, 0x33, 0x38, 0x61, 0x00 }; /* * MMS M-Send.Conf PDU 2 * This PDU shows the decoding of a M-Send.Conf PDU with an rejected "response * status" and a below content: * Overall message size: 13 * MMS message type: send-conf * MMS transaction id: 31888 * MMS version: 1.0 * Response-Status: error-permanent-message-format-corrupt * Msg-Id: (null) */ static const unsigned char mms_m_send_conf_2[] = { 0x8C, 0x81, 0x98, 0x33, 0x31, 0x38, 0x38, 0x38, 0x00, 0x8D, 0x90, 0x92, 0xE2 }; /* * MMS delivery_ind PDU 1 * This PDU is a delivery notification * MMS message type: delivery_ind * MMS version: 1.2 * To: +33666565565/TYPE=PLMN * Msg-Id: mavodi-7-88-19-7-9c-60c31484-4695ec471ce * Date: 2021-06-04T21:16:06-0400 * Delivery Report status: 129 */ static const char mms_delivery_ind_1[] = "./unit/di-mms-1.mms"; struct mms_test { const char *pathname; const unsigned char *pdu; unsigned int len; }; static const struct mms_test mms_m_notify_ind_test_1 = { .pdu = mms_m_notify_ind_1, .len = sizeof(mms_m_notify_ind_1), }; static const struct mms_test mms_m_notify_ind_test_2 = { .pdu = mms_m_notify_ind_2, .len = sizeof(mms_m_notify_ind_2), }; static const struct mms_test mms_m_notify_ind_test_3 = { .pathname = mms_m_notify_ind_3, }; static const struct mms_test mms_m_notify_ind_test_4 = { .pathname = mms_m_notify_ind_4, }; static const struct mms_test mms_m_retrieve_conf_test_1 = { .pdu = mms_m_retrieve_conf_1, .len = sizeof(mms_m_retrieve_conf_1), }; static const struct mms_test mms_m_retrieve_conf_test_2 = { .pdu = mms_m_retrieve_conf_2, .len = sizeof(mms_m_retrieve_conf_2), }; static const struct mms_test mms_m_retrieve_conf_test_3 = { .pdu = mms_m_retrieve_conf_3, .len = sizeof(mms_m_retrieve_conf_3), }; static const struct mms_test mms_m_retrieve_conf_test_4 = { .pathname = mms_m_retrieve_conf_4, }; static const struct mms_test mms_m_retrieve_conf_test_5 = { .pathname = mms_m_retrieve_conf_5, }; static const struct mms_test mms_m_retrieve_conf_test_6 = { .pathname = mms_m_retrieve_conf_6, }; static const struct mms_test mms_m_retrieve_conf_test_7 = { .pathname = mms_m_retrieve_conf_7, }; static const struct mms_test mms_m_retrieve_conf_test_8 = { .pathname = mms_m_retrieve_conf_8, }; static const struct mms_test mms_m_retrieve_conf_test_9 = { .pathname = mms_m_retrieve_conf_9, }; static const struct mms_test mms_m_retrieve_conf_test_10 = { .pathname = mms_m_retrieve_conf_10, }; static const struct mms_test mms_m_retrieve_conf_test_11 = { .pathname = mms_m_retrieve_conf_11, }; static const struct mms_test mms_m_retrieve_conf_test_12 = { .pathname = mms_m_retrieve_conf_12, }; static const struct mms_test mms_m_retrieve_conf_test_13 = { .pathname = mms_m_retrieve_conf_13, }; static const struct mms_test mms_m_retrieve_conf_test_14 = { .pathname = mms_m_retrieve_conf_14, }; static const struct mms_test mms_m_retrieve_conf_test_15 = { .pathname = mms_m_retrieve_conf_15, }; static const struct mms_test mms_m_send_req_dec_test_1 = { .pathname = mms_m_send_req_1, }; static const struct mms_test mms_m_send_req_dec_test_2 = { .pathname = mms_m_send_req_2, }; static const struct mms_test mms_m_send_conf_test_1 = { .pdu = mms_m_send_conf_1, .len = sizeof(mms_m_send_conf_1), }; static const struct mms_test mms_m_send_conf_test_2 = { .pdu = mms_m_send_conf_2, .len = sizeof(mms_m_send_conf_2), }; static const struct mms_test mms_delivery_ind_test_1 = { .pathname = mms_delivery_ind_1, }; static void test_decode_mms (gconstpointer data) { const struct mms_test *test = data; struct mms_message *msg; unsigned int len; gboolean ret; msg = g_try_new0 (struct mms_message, 1); if (msg == NULL) { g_printerr ("Failed allocate message"); return; } if (test->pathname != NULL) { struct stat st; unsigned char *pdu; int fd; fd = open (test->pathname, O_RDONLY); if (fd < 0) { g_printerr ("Failed to open %s\n", test->pathname); goto out; } if (fstat (fd, &st) < 0) { g_printerr ("Failed to stat %s\n", test->pathname); close (fd); goto out; } len = st.st_size; pdu = mmap (NULL, len, PROT_READ, MAP_SHARED, fd, 0); if (!pdu || pdu == MAP_FAILED) { g_printerr ("Failed to mmap %s\n", test->pathname); close (fd); goto out; } ret = mms_message_decode (pdu, len, msg); munmap (pdu, len); close (fd); } else { const unsigned char *pdu = test->pdu; len = test->len; ret = mms_message_decode (pdu, len, msg); } g_assert (ret == TRUE); if (g_test_verbose ()) { g_print ("Overall message size: %d\n", len); g_print ("MMS message type: %s\n", message_type_to_string (msg->type)); g_print ("MMS transaction id: %s\n", msg->transaction_id); g_print ("MMS version: %u.%u\n", (msg->version & 0x70) >> 4, msg->version & 0x0f); switch (msg->type) { case MMS_MESSAGE_TYPE_NOTIFICATION_IND: dump_notification_ind (msg); break; case MMS_MESSAGE_TYPE_RETRIEVE_CONF: dump_retrieve_conf (msg); break; case MMS_MESSAGE_TYPE_SEND_CONF: dump_send_conf (msg); break; case MMS_MESSAGE_TYPE_SEND_REQ: dump_send_req (msg); break; case MMS_MESSAGE_TYPE_DELIVERY_IND: dump_delivery_ind (msg); break; case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: default: break; } } out: mms_message_free (msg); } struct mms_encode_test { struct mms_message msg; const unsigned char pdu[]; }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" static const struct mms_encode_test mms_m_notifyresp_ind_test_1 = { .msg = { .type = MMS_MESSAGE_TYPE_NOTIFYRESP_IND, .uuid = NULL, .path = NULL, .transaction_id = "0123456789abcdef", .version = MMS_MESSAGE_VERSION_1_2, .attachments = NULL, {.nri = { .notify_status = MMS_MESSAGE_NOTIFY_STATUS_RETRIEVED, } } }, .pdu = { 0x8C, 0x83, 0x98, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x00, 0x8D, 0x92, 0x95, 0x81} }; #define CT_MUTLIPART "Content-Type: \"application/vnd.wap.multipart." #define CT_TYPE ";type=\"application/smil\"" #define CT_START ";start=\"\"" #define CT_MULTIPART_RELATED CT_MUTLIPART "related\"" CT_TYPE CT_START #define CT_MULTIPART_MIXED CT_MUTLIPART "mixed\"" static const struct mms_encode_test mms_m_send_req_test_1 = { .msg = { .type = MMS_MESSAGE_TYPE_SEND_REQ, .uuid = NULL, .path = NULL, .transaction_id = "0123456789abcdef_sr1", .version = MMS_MESSAGE_VERSION_1_2, .attachments = NULL, {.sr = { .status = MMS_MESSAGE_STATUS_DRAFT, .to = "0123456789", .dr = TRUE, .subject = "", .content_type = CT_MULTIPART_MIXED, } } }, .pdu = { 0x8C, 0x80, 0x98, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x5F, 0x73, 0x72, 0x31, 0x00, 0x8D, 0x92, 0x89, 0x01, 0x81, 0x97, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, 0x86, 0x80, 0x84, 0xA3} }; static const struct mms_encode_test mms_m_send_req_test_2 = { .msg = { .type = MMS_MESSAGE_TYPE_SEND_REQ, .uuid = NULL, .path = NULL, .transaction_id = "0123456789abcdef_sr1", .version = MMS_MESSAGE_VERSION_1_2, .attachments = NULL, {.sr = { .status = MMS_MESSAGE_STATUS_DRAFT, .to = "0123456789", .dr = TRUE, .subject = "Subject Test", .content_type = CT_MULTIPART_MIXED, } } }, .pdu = { 0x8c, 0x80, 0x98, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x5f, 0x73, 0x72, 0x31, 0x00, 0x8d, 0x92, 0x89, 0x01, 0x81, 0x97, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00, 0x96, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74, 0x00, 0x86, 0x80, 0x84, 0xa3} }; #pragma GCC diagnostic pop static void test_encode_mms (gconstpointer data) { struct mms_encode_test *test_msg = (struct mms_encode_test *) data; char *filepath; gboolean ret; int fd; filepath = g_strdup_printf ("%s/.mms/mms_XXXXXX.tmp", g_get_home_dir ()); if (filepath == NULL) return; fd = g_mkstemp_full (filepath, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR); if (fd < 0) { g_free (filepath); return; } if (g_test_verbose ()) g_print ("tmp filename : %s\n", filepath); ret = mms_message_encode (&test_msg->msg, fd); close (fd); if (ret == TRUE) ret = check_encoded_msg (filepath, test_msg->pdu); unlink (filepath); g_free (filepath); g_assert (ret == TRUE); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_test_add_data_func ("/mmsutil/Decode MMS M-Notify.Ind PDU 1", &mms_m_notify_ind_test_1, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Notify.Ind PDU 2", &mms_m_notify_ind_test_2, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Notify.Ind PDU 3", &mms_m_notify_ind_test_3, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Notify.Ind PDU 4", &mms_m_notify_ind_test_4, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 1", &mms_m_retrieve_conf_test_1, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 2", &mms_m_retrieve_conf_test_2, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 3", &mms_m_retrieve_conf_test_3, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 4", &mms_m_retrieve_conf_test_4, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 5", &mms_m_retrieve_conf_test_5, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 6", &mms_m_retrieve_conf_test_6, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 7", &mms_m_retrieve_conf_test_7, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 8", &mms_m_retrieve_conf_test_8, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 9", &mms_m_retrieve_conf_test_9, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 10", &mms_m_retrieve_conf_test_10, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 11", &mms_m_retrieve_conf_test_11, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 12", &mms_m_retrieve_conf_test_12, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 13", &mms_m_retrieve_conf_test_13, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 14", &mms_m_retrieve_conf_test_14, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Retrieve.Conf PDU 15", &mms_m_retrieve_conf_test_15, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Send.Req PDU 1", &mms_m_send_req_dec_test_1, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Send.Req PDU 2", &mms_m_send_req_dec_test_2, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS Delivery Indication PDU 1", &mms_delivery_ind_test_1, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Send.Conf PDU 1", &mms_m_send_conf_test_1, test_decode_mms); g_test_add_data_func ("/mmsutil/Decode MMS M-Send.Conf PDU 2", &mms_m_send_conf_test_2, test_decode_mms); g_test_add_data_func ("/mmsutil/Encode MMS M-NotifyResp.Ind 1", &mms_m_notifyresp_ind_test_1, test_encode_mms); g_test_add_data_func ("/mmsutil/Encode MMS M-SendReq 1", &mms_m_send_req_test_1, test_encode_mms); g_test_add_data_func ("/mmsutil/Encode MMS M-SendReq 2", &mms_m_send_req_test_2, test_encode_mms); return g_test_run (); } mmsd-2.6.0/unit/test-phone-utils.c000066400000000000000000000073331456374454100170510ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include #include #include #include #include #include #include "phone-utils.h" static void test_number_decode (gconstpointer data) { char *output; const char phone_test1[] = "12065550110"; const char phone_test2[] = "2065550110"; const char phone_test3[] = "(206) 555-0110"; const char phone_test4[] = "206-555-0110"; /* There's a bug where a comma can leak in. */ const char phone_test5[] = "206-555-0110,"; const char phone_output[] = "+12065550110"; const char email_test1[] = "testemail@example.com"; //Free Mobile FR Dest Number const char carrer_vvm_test1[] = "2050"; //AT&T US const char carrer_vvm_test2[] = "94183567"; //T-mobile US and Mint Mobile US const char carrer_vvm_test3[] = "127"; //Ting US const char carrer_vvm_test4[] = "122"; //VZW USA US const char carrer_vvm_test5[] = "900080006200"; /* There's a bug where a comma can leak in. */ const char carrer_vvm_test6[] = "127,"; output = phone_utils_format_number_e164 (phone_test1, "US", FALSE); g_assert_cmpstr (output, ==, phone_output); output = phone_utils_format_number_e164 (phone_test2, "US", FALSE); g_assert_cmpstr (output, ==, phone_output); output = phone_utils_format_number_e164 (phone_test3, "US", FALSE); g_assert_cmpstr (output, ==, phone_output); output = phone_utils_format_number_e164 (phone_test4, "US", FALSE); g_assert_cmpstr (output, ==, phone_output); output = phone_utils_format_number_e164 (phone_test5, "US", FALSE); g_assert_cmpstr (output, ==, phone_output); output = phone_utils_format_number_e164 (phone_output, "US", FALSE); g_assert_cmpstr (output, ==, phone_output); output = phone_utils_format_number_e164 (email_test1, "US", TRUE); g_assert_cmpstr (output, ==, email_test1); output = phone_utils_format_number_e164 (email_test1, "US", FALSE); g_assert_null (output); output = phone_utils_format_number_e164 (carrer_vvm_test1, "FR", FALSE); g_assert_cmpstr (output, ==, carrer_vvm_test1); output = phone_utils_format_number_e164 (carrer_vvm_test2, "US", FALSE); g_assert_cmpstr (output, ==, carrer_vvm_test2); output = phone_utils_format_number_e164 (carrer_vvm_test3, "US", FALSE); g_assert_cmpstr (output, ==, carrer_vvm_test3); output = phone_utils_format_number_e164 (carrer_vvm_test4, "US", FALSE); g_assert_cmpstr (output, ==, carrer_vvm_test4); output = phone_utils_format_number_e164 (carrer_vvm_test5, "US", FALSE); g_assert_cmpstr (output, ==, carrer_vvm_test5); output = phone_utils_format_number_e164 (carrer_vvm_test6, "US", FALSE); g_assert_cmpstr (output, ==, carrer_vvm_test3); } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_test_add_data_func ("/mmsutil/Number Decode Test", NULL, test_number_decode); return g_test_run (); } mmsd-2.6.0/unit/test-service-providers.c000066400000000000000000000165671456374454100202660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2019 Red Hat */ #ifdef HAVE_CONFIG_H #include #endif #include #include "service-providers.h" static void test_seperate_mms_apn_cb (const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); g_assert_no_error (error); g_assert_cmpstr (apn, ==, "mms"); g_assert_cmpstr (mmsc, ==, "http://mms.seperate.example.com/"); g_assert_cmpstr (proxy, ==, "192.0.2.1:8080"); g_assert_null (mmssize); } /* * This tests an APN that has seperate internet and MMS. It lacks the * "mmsattachmentsize" attribute. It has an MMS proxy. */ static void test_seperate_mms_apn (void) { GMainLoop *loop = g_main_loop_new (NULL, FALSE); mmsd_service_providers_find_settings (SOURCE_ROOT "/unit/test-service-providers.xml", "133666", "mms", test_seperate_mms_apn_cb, loop); g_main_loop_run (loop); g_main_loop_unref (loop); } /*****************************************************************************/ static void test_combined_mms_apn_cb (const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); g_assert_no_error (error); g_assert_cmpstr (apn, ==, "access.example.com"); g_assert_cmpstr (mmsc, ==, "http://mms.combined.example.com/"); g_assert_null (proxy); g_assert_cmpstr (mmssize, ==, "1048576"); g_assert_cmpint(1048576, ==, atoi(mmssize)); } /* * This tests an APN that has internet and MMS under the same APN. It also has * the "mmsattachmentsize" attribute. It does not have an MMS proxy. */ static void test_combined_mms_apn (void) { GMainLoop *loop = g_main_loop_new (NULL, FALSE); mmsd_service_providers_find_settings (SOURCE_ROOT "/unit/test-service-providers.xml", "345999", "access.example.com", test_combined_mms_apn_cb, loop); g_main_loop_run (loop); g_main_loop_unref (loop); } /*****************************************************************************/ static void test_missing_mmsc_apn_cb (const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); g_assert_error (error, 1, 1); } /* * The MCC/MNC 123123 exists, and the APN "access.example.com" exists in there, * but there is no MMSC under it, so this will not work */ static void test_missing_mmsc_apn (void) { GMainLoop *loop = g_main_loop_new (NULL, FALSE); mmsd_service_providers_find_settings (SOURCE_ROOT "/unit/test-service-providers.xml", "123123", "access.example.com", test_missing_mmsc_apn_cb, loop); g_main_loop_run (loop); g_main_loop_unref (loop); } /*****************************************************************************/ static void test_missing_mmsc_apn_two_cb (const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); g_assert_error (error, 1, 1); } /* * The MCC/MNC 13337 exists, and the APN "gprs.example.com" exists in there, * but there is no MMSC under it, so this will not work */ static void test_missing_mmsc_apn_two (void) { GMainLoop *loop = g_main_loop_new (NULL, FALSE); mmsd_service_providers_find_settings (SOURCE_ROOT "/unit/test-service-providers.xml", "13337", "gprs.example.com", test_missing_mmsc_apn_two_cb, loop); g_main_loop_run (loop); g_main_loop_unref (loop); } /*****************************************************************************/ static void test_negative_cb (const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); g_assert_error (error, 1, 1); } /* * The MCC/MNC 78130 does not exist. */ static void test_negative (void) { GMainLoop *loop = g_main_loop_new (NULL, FALSE); mmsd_service_providers_find_settings (SOURCE_ROOT "/unit/test-service-providers.xml", "78130", "gprs.example.com", test_negative_cb, loop); g_main_loop_run (loop); g_main_loop_unref (loop); } /*****************************************************************************/ static void test_nonexistent_cb (const char *apn, const char *mmsc, const char *proxy, const char *mmssize, GError *error, gpointer user_data) { GMainLoop *loop = user_data; g_assert_error (error, G_IO_ERROR, G_IO_ERROR_AGAIN); g_main_loop_quit (loop); } /* * Test to see what happens when you point to the wrong xml file, it won't * exist */ static void test_nonexistent (void) { GMainLoop *loop = g_main_loop_new (NULL, FALSE); mmsd_service_providers_find_settings ("nonexistent.xml", "13337", "gprs.example.com", test_nonexistent_cb, loop); g_main_loop_run (loop); g_main_loop_unref (loop); } /*****************************************************************************/ int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/service-providers/seperate_mms_apn", test_seperate_mms_apn); g_test_add_func ("/service-providers/combined_mms_apn", test_combined_mms_apn); g_test_add_func ("/service-providers/missing_mmsc_apn", test_missing_mmsc_apn); g_test_add_func ("/service-providers/missing_mmsc_apn_two", test_missing_mmsc_apn_two); g_test_add_func ("/service-providers/negative", test_negative); g_test_add_func ("/service-providers/nonexistent", test_nonexistent); return g_test_run (); } mmsd-2.6.0/unit/test-service-providers.xml000066400000000000000000000042011456374454100206220ustar00rootroot00000000000000 Sophia APN 192.0.2.1 192.0.2.2 Demiurge Seperate MMS APN mms mms http://mms.seperate.example.com/ 192.0.2.1:8080 Seperate Internet APN praise santa 192.0.2.1 192.0.2.2 192.0.2.3 Second worship doom Personal 1234567 098765 //VVM Other worship doom Combined MMS and Internet APN 192.0.2.1 192.0.2.2 http://mms.combined.example.com/ 1048576 mmsd-2.6.0/unit/test-wsputil.c000066400000000000000000000272141456374454100163110ustar00rootroot00000000000000/* * * Multimedia Messaging Service Daemon - The Next Generation * * Copyright (C) 2010-2011, Intel Corporation * 2021, Chris Talbot * * 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. * * 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 #endif #include #include #include "wsputil.h" static void dump_field (enum wsp_value_type type, const void *data, unsigned int len) { switch (type) { case WSP_VALUE_TYPE_LONG: { unsigned int i; const unsigned char *l = data; for (i = 0; i < len; i++) { g_print ("%02x ", l[i]); if ((i % 32) == 31) g_print ("\n"); } g_print ("\n"); break; } case WSP_VALUE_TYPE_SHORT: { const unsigned char *s = data; g_print ("%02x\n", s[0] & 0x7f); break; } case WSP_VALUE_TYPE_TEXT: g_print ("%s\n", (const char *) data); break; default: g_warning ("Unhandled Case"); break; } g_print ("Field length: %d\n", len); } static const unsigned char push1[] = { 0x01, 0x06, 0x24, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x76, 0x6E, 0x64, 0x2E, 0x77, 0x61, 0x70, 0x2E, 0x6D, 0x6D, 0x73, 0x2D, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xAF, 0x84, 0xB4, 0x86, 0x8C, 0x82, 0x98, 0x4F, 0x67, 0x51, 0x4B, 0x4B, 0x42, 0x00, 0x8D, 0x90, 0x89, 0x08, 0x80, 0x45, 0x72, 0x6F, 0x74, 0x69, 0x6B, 0x00, 0x96, 0x50, 0x69, 0x6E, 0x2D, 0x55, 0x70, 0x73, 0x00, 0x8A, 0x80, 0x8E, 0x02, 0x40, 0x00, 0x88, 0x05, 0x81, 0x03, 0x03, 0xF4, 0x80, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x65, 0x70, 0x73, 0x33, 0x2E, 0x64, 0x65, 0x2F, 0x4F, 0x2F, 0x5A, 0x39, 0x49, 0x5A, 0x4F, 0x00 }; static const unsigned char push2[] = { 0x00, 0x06, 0x24, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x76, 0x6E, 0x64, 0x2E, 0x77, 0x61, 0x70, 0x2E, 0x6D, 0x6D, 0x73, 0x2D, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x00, 0xAF, 0x84, 0x8D, 0xDC, 0x8C, 0x82, 0x98, 0x77, 0x4C, 0x4A, 0x65, 0x54, 0x37, 0x54, 0x48, 0x75, 0x00, 0x8D, 0x90, 0x89, 0x17, 0x80, 0x31, 0x35, 0x35, 0x35, 0x31, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x2F, 0x54, 0x59, 0x50, 0x45, 0x3D, 0x50, 0x4C, 0x4D, 0x4E, 0x00, 0x8A, 0x80, 0x8E, 0x02, 0x5A, 0x1D, 0x88, 0x05, 0x81, 0x03, 0x03, 0xF4, 0x80, 0x83, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6D, 0x6D, 0x73, 0x63, 0x31, 0x31, 0x3A, 0x31, 0x30, 0x30, 0x32, 0x31, 0x2F, 0x6D, 0x6D, 0x73, 0x63, 0x2F, 0x31, 0x5F, 0x31, 0x3F, 0x77, 0x4C, 0x4A, 0x65, 0x54, 0x37, 0x54, 0x48, 0x75, 0x00 }; static const unsigned char push3[] = { 0x4C, 0x06, 0x03, 0xCB, 0xAF, 0x88, 0x03, 0x0E, 0x6A, 0x00, 0xC5, 0x05, 0x85, 0x06, 0x86, 0x07, 0x87, 0x01, 0x46, 0x47, 0x03, 0x31, 0x2E, 0x30, 0x00, 0x01, 0x01, 0x49, 0x4A, 0x46, 0x48, 0x03, 0x63, 0x69, 0x64, 0x3A, 0x32, 0x30, 0x30, 0x35, 0x40, 0x67, 0x72, 0x61, 0x6E, 0x6A, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x00, 0x01, 0x01, 0x4B, 0x4C, 0xC3, 0x10, 0xF1, 0xF7, 0x7C, 0x37, 0x95, 0xD3, 0x39, 0x65, 0x84, 0x1E, 0x4A, 0x27, 0xA6, 0xC2, 0x71, 0xDB, 0x01, 0x01, 0x01, 0x4D, 0x4F, 0x52, 0x53, 0x03, 0x32, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; static const unsigned char push4[] = { 0xB7, 0x06, 0x03, 0xC4, 0xAF, 0x87, 0x00, 0xEF, 0xC4, 0x29, 0x75, 0x46, 0xCA, 0xD6, 0xC5, 0x51, 0x08, 0xF1, 0x60, 0xBD, 0xB8, 0x00, 0x03, 0x38, 0x00, 0x00, 0x00, 0x00, 0x48, 0x08, 0x66, 0x75, 0x6E, 0x61, 0x6D, 0x62, 0x6F, 0x6C }; static const unsigned char push5[] = { 0xC4, 0x06, 0x2F, 0x1F, 0x2D, 0xB6, 0x91, 0x81, 0x92, 0x35, 0x39, 0x44, 0x33, 0x36, 0x44, 0x31, 0x34, 0x45, 0x38, 0x38, 0x34, 0x39, 0x39, 0x41, 0x33, 0x44, 0x35, 0x36, 0x45, 0x42, 0x41, 0x36, 0x44, 0x34, 0x46, 0x39, 0x34, 0x41, 0x36, 0x37, 0x30, 0x34, 0x31, 0x42, 0x44, 0x32, 0x41, 0x39, 0x43, 0x00, 0x03, 0x0B, 0x6A, 0x00, 0x45, 0xC6, 0x00, 0x01, 0x55, 0x01, 0x87, 0x36, 0x06, 0x03, 0x77, 0x32, 0x00, 0x01, 0x87, 0x22, 0x06, 0x03, 0x49, 0x4E, 0x54, 0x45, 0x52, 0x4E, 0x45, 0x54, 0x00, 0x01, 0xC6, 0x59, 0x01, 0x87, 0x3A, 0x06, 0x03, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x75, 0x73, 0x65, 0x72, 0x70, 0x69, 0x6E, 0x31, 0x32, 0x33, 0x34, 0x00, 0x01, 0x87, 0x1C, 0x01, 0x01, 0x01, 0x01, }; struct push_test { const unsigned char *pdu; unsigned int len; }; /* * MMS Push 1 * This PDU shows a MMS push message with below content: * Overall Push Length: 107 * TID: 1 * Type: 6 * Push Content + Header Length: 36 * Content-Type: application/vnd.wap.mms-message * Header: Well known: 0x2F -> X-Wap-Application-Id * Value: 0x04 -> x-wap-application:mms.ua * Field length: 1 * Header: Well known: 0x34 -> Push-Flag * Value: 0x06 * Field length: 1 * Body Length: 68 */ static const struct push_test push_test_1 = { .pdu = push1, .len = sizeof(push1), }; /* * MMS Push 2 * This PDU shows a MMS push message with below content: * Overall Push Length: 132 * TID: 0 * Type: 6 * Push Content + Header Length: 36 * Content-Type: application/vnd.wap.mms-message * Header: Well known: 0x2F -> X-Wap-Application-Id * Value: 0x04 -> x-wap-application:mms.ua * Field length: 1 * Header: Well known: 0x0D -> Content-Length * Value: 0x5C * Field length: 1 * Body Length: 93 */ static const struct push_test push_test_2 = { .pdu = push2, .len = sizeof(push2), }; /* * DRM Push * This PDU shows a DRM push message with below content: * DRM Push * Overall Push Length: 90 * TID: 76 * Type: 6 * Push Content + Header Length: 3 * Content-Type: application/vnd.oma.drm.rights+wbxml * Header: Well known: 0x2F -> X-Wap-Application-Id * Value: 0x08 -> x-wap-application:drm.ua * Field length: 1 * Body Length: 84 */ static const struct push_test push_test_3 = { .pdu = push3, .len = sizeof(push3), }; /* * DM Push * This PDU shows a DM push message with below content: * Overall Push Length: 38 * TID: 183 * Type: 6 * Push Content + Header Length: 3 * Content-Type: application/vnd.syncml.notification * Header: Well known: 0x2F -> X-Wap-Application-Id * Value: 0x07 -> x-wap-application:syncml.dm * Field length: 1 * Body Length: 32 */ static const struct push_test push_test_4 = { .pdu = push4, .len = sizeof(push4), }; /* * CP Push * This PDU shows a CP push message with below content: * Overall Push Length: 115 * TID: 196 * Type: 6 * Push Content + Header Length: 47 * Content-Type: application/vnd.wap.connectivity-wbxml * Body Length: 65 */ static const struct push_test push_test_5 = { .pdu = push5, .len = sizeof(push5), }; static void test_decode_push (gconstpointer data) { const struct push_test *test = data; const unsigned char *pdu = test->pdu; unsigned int len = test->len; unsigned int headerslen; const void *content_data; struct wsp_header_iter iter; gboolean ret; unsigned int nread; unsigned int consumed; unsigned int param_len; g_assert (pdu[1] == 0x06); /* Consume TID and Type */ nread = 2; ret = wsp_decode_uintvar (pdu + nread, len, &headerslen, &consumed); g_assert (ret == TRUE); /* Consume uintvar bytes */ nread += consumed; if (g_test_verbose ()) { g_print ("Overall Push Length: %d\n", len); g_print ("TID: %d\n", pdu[0]); g_print ("Type: %d\n", pdu[1]); g_print ("Push Content + Header Length: %d\n", headerslen); } ret = wsp_decode_content_type (pdu + nread, headerslen, &content_data, &consumed, ¶m_len); g_assert (ret == TRUE); /* Consume Content Type bytes, including parameters */ consumed += param_len; nread += consumed; if (g_test_verbose ()) g_print ("Content-Type: %s\n", (const char *) content_data); wsp_header_iter_init (&iter, pdu + nread, headerslen - consumed, 0); while (wsp_header_iter_next (&iter)) { const void *hdr = wsp_header_iter_get_hdr (&iter); const void *val = wsp_header_iter_get_val (&iter); const unsigned char *wk; const void *urn; if (g_test_verbose ()) g_print ("Header: "); switch (wsp_header_iter_get_hdr_type (&iter)) { case WSP_HEADER_TYPE_WELL_KNOWN: wk = hdr; if (g_test_verbose ()) g_print ("Well known %02x\n", wk[0] & 0x7f); if ((wk[0] & 0x7f) == WSP_HEADER_TOKEN_APP_ID) { ret = wsp_decode_application_id (&iter, &urn); g_assert (ret == TRUE); if (g_test_verbose ()) g_print ("app_id=%s\n", (const char *)urn); } break; case WSP_HEADER_TYPE_APPLICATION: if (g_test_verbose ()) g_print ("Application: %s\n", (const char *) hdr); break; default: g_warning ("Unhandled Case"); break; } if (g_test_verbose ()) { g_print ("Value: "); dump_field (wsp_header_iter_get_val_type (&iter), val, wsp_header_iter_get_val_len (&iter)); } } if (g_test_verbose ()) g_print ("Body Length: %d\n", len - nread - headerslen + consumed); } struct text_header_iter_test { const char *header; gboolean success; const char *key; const char *value; const char *dict[]; }; static const struct text_header_iter_test text_header_iter_1 = { .header = "Content-Type: \"text/html\"; charset=ISO-8859-4; q; bar", .success = TRUE, .key = "Content-Type", .value = "text/html", .dict = { "charset", "ISO-8859-4", "q", NULL, "bar", NULL, NULL, NULL }, }; static void test_wsp_text_header_iter (gconstpointer data) { const struct text_header_iter_test *test = data; struct wsp_text_header_iter iter; gboolean r; int i; r = wsp_text_header_iter_init (&iter, test->header); g_assert (r == test->success); if (r == FALSE) return; g_assert_cmpstr (test->key, ==, wsp_text_header_iter_get_key (&iter)); g_assert_cmpstr (test->value, ==, wsp_text_header_iter_get_value (&iter)); for (i = 0; test->dict[i] != NULL; i++) { r = wsp_text_header_iter_param_next (&iter); g_assert (r == TRUE); g_assert_cmpstr (test->dict[i++], ==, wsp_text_header_iter_get_key (&iter)); g_assert_cmpstr (test->dict[i], ==, wsp_text_header_iter_get_value (&iter)); } } int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_test_add_data_func ("/wsputil/Decode MMS Push 1", &push_test_1, test_decode_push); g_test_add_data_func ("/wsputil/Decode MMS Push 2", &push_test_2, test_decode_push); g_test_add_data_func ("/wsputil/Decode DRM Push", &push_test_3, test_decode_push); g_test_add_data_func ("/wsputil/Decode DM Push", &push_test_4, test_decode_push); g_test_add_data_func ("/wsputil/Decode CP Push", &push_test_5, test_decode_push); g_test_add_data_func ("/wsputil/WSP Text Header Iter Test 1", &text_header_iter_1, test_wsp_text_header_iter); return g_test_run (); }