├── .gitignore ├── .idea └── codeStyles │ └── codeStyleConfig.xml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── xaps-daemon.c ├── xaps-daemon.h ├── xaps-imap-plugin.c ├── xaps-imap-plugin.h ├── xaps-push-notification-plugin.c ├── xaps-push-notification-plugin.h └── xaps.conf /.gitignore: -------------------------------------------------------------------------------- 1 | ### CMake template 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | ### C++ template 12 | # Prerequisites 13 | *.d 14 | 15 | # Compiled Object files 16 | *.slo 17 | *.lo 18 | *.o 19 | *.obj 20 | 21 | # Precompiled Headers 22 | *.gch 23 | *.pch 24 | 25 | # Compiled Dynamic libraries 26 | *.so 27 | *.dylib 28 | *.dll 29 | 30 | # Fortran module files 31 | *.mod 32 | *.smod 33 | 34 | # Compiled Static libraries 35 | *.lai 36 | *.la 37 | *.a 38 | *.lib 39 | 40 | # Executables 41 | *.exe 42 | *.out 43 | *.app 44 | ### C template 45 | # Prerequisites 46 | *.d 47 | 48 | # Object files 49 | *.o 50 | *.ko 51 | *.obj 52 | *.elf 53 | 54 | # Linker output 55 | *.ilk 56 | *.map 57 | *.exp 58 | 59 | # Precompiled Headers 60 | *.gch 61 | *.pch 62 | 63 | # Libraries 64 | *.lib 65 | *.a 66 | *.la 67 | *.lo 68 | 69 | # Shared objects (inc. Windows DLLs) 70 | *.dll 71 | *.so 72 | *.so.* 73 | *.dylib 74 | 75 | # Executables 76 | *.exe 77 | *.out 78 | *.app 79 | *.i*86 80 | *.x86_64 81 | *.hex 82 | 83 | # Debug files 84 | *.dSYM/ 85 | *.su 86 | *.idb 87 | *.pdb 88 | 89 | # Kernel Module Compile Results 90 | *.mod* 91 | *.cmd 92 | .tmp_versions/ 93 | modules.order 94 | Module.symvers 95 | Mkfile.old 96 | dkms.conf 97 | ### JetBrains template 98 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 99 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 100 | 101 | # User-specific stuff 102 | .idea/**/workspace.xml 103 | .idea/**/tasks.xml 104 | .idea/**/usage.statistics.xml 105 | .idea/**/dictionaries 106 | .idea/**/shelf 107 | 108 | # Sensitive or high-churn files 109 | .idea/**/dataSources/ 110 | .idea/**/dataSources.ids 111 | .idea/**/dataSources.local.xml 112 | .idea/**/sqlDataSources.xml 113 | .idea/**/dynamic.xml 114 | .idea/**/uiDesigner.xml 115 | .idea/**/dbnavigator.xml 116 | 117 | # Gradle 118 | .idea/**/gradle.xml 119 | .idea/**/libraries 120 | 121 | # Gradle and Maven with auto-import 122 | # When using Gradle or Maven with auto-import, you should exclude module files, 123 | # since they will be recreated, and may cause churn. Uncomment if using 124 | # auto-import. 125 | # .idea/modules.xml 126 | # .idea/*.iml 127 | # .idea/modules 128 | 129 | # CMake 130 | cmake-build-*/ 131 | 132 | # Mongo Explorer plugin 133 | .idea/**/mongoSettings.xml 134 | 135 | # File-based project format 136 | *.iws 137 | 138 | # IntelliJ 139 | out/ 140 | 141 | # mpeltonen/sbt-idea plugin 142 | .idea_modules/ 143 | 144 | # JIRA plugin 145 | atlassian-ide-plugin.xml 146 | 147 | # Cursive Clojure plugin 148 | .idea/replstate.xml 149 | 150 | # Crashlytics plugin (for Android Studio and IntelliJ) 151 | com_crashlytics_export_strings.xml 152 | crashlytics.properties 153 | crashlytics-build.properties 154 | fabric.properties 155 | 156 | # Editor-based Rest Client 157 | .idea/httpRequests -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6 FATAL_ERROR) 2 | project(dovecot-xaps-plugin) 3 | 4 | if (APPLE) 5 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_client_read_args") 6 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_client_send_command_error") 7 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_client_send_line") 8 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_client_send_tagline") 9 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_command_register") 10 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_command_unregister") 11 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_imap_client_created_hook_set") 12 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_push_notification_driver_debug") 13 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_push_notification_driver_register") 14 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_push_notification_driver_unregister") 15 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_push_notification_event_init") 16 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U,_push_notification_events") 17 | endif () 18 | 19 | include_directories(/usr/include/dovecot) 20 | include_directories(/usr/local/include/dovecot) 21 | find_library(LIBDOVECOT dovecot /usr/lib/dovecot/ /usr/local/lib/dovecot/) 22 | find_library(LIBDOVECOTSTORAGE dovecot-storage /usr/lib/dovecot/ /usr/local/lib/dovecot/) 23 | 24 | set(CMAKE_C_STANDARD 99) 25 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") 26 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 27 | 28 | add_library(lib25_xaps_push_notification_plugin MODULE xaps-daemon.c xaps-push-notification-plugin.c) 29 | add_library(lib25_xaps_imap_plugin MODULE xaps-daemon.c xaps-imap-plugin.c) 30 | 31 | target_link_libraries(lib25_xaps_push_notification_plugin ${LIBDOVECOT} ${LIBDOVECOTSTORAGE}) 32 | target_link_libraries(lib25_xaps_imap_plugin ${LIBDOVECOT} ${LIBDOVECOTSTORAGE}) 33 | 34 | set_target_properties(lib25_xaps_push_notification_plugin PROPERTIES PREFIX "") 35 | set_target_properties(lib25_xaps_imap_plugin PROPERTIES PREFIX "") 36 | 37 | install(TARGETS lib25_xaps_push_notification_plugin DESTINATION /usr/lib/dovecot/modules) 38 | install(TARGETS lib25_xaps_imap_plugin DESTINATION /usr/lib/dovecot/modules) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Stefan Arentz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iOS Push Email for Dovecot 2 | ========================== 3 | 4 | What is this? 5 | ------------- 6 | 7 | This project, together with the [dovecot-xaps-daemon](https://github.com/st3fan/dovecot-xaps-daemon) project, will enable push email for iOS devices that talk to your Dovecot 2.0.x IMAP server. This is specially useful for people who are migrating away from running email services on OS X Server and want to keep the Push Email ability. 8 | 9 | > Please note that it is not possible to use this project without legally owning a copy of OS X Server. You can purchase OS X Server on the [Mac App Store](https://itunes.apple.com/ca/app/os-x-server/id714547929?mt=12) or download it for free if you are a registered Mac or iOS developer. 10 | 11 | High Level Overview 12 | ------------------- 13 | 14 | There are two parts to enabling iOS Push Email. You will need both parts for this to work. 15 | 16 | First you need to install the Dovecot plugins from this project. The Dovecot plugins add support for the `XAPPLEPUSHSERVICE` IMAP extension that will let iOS devices register themselves to receive native push notifications for new email arrival. 17 | 18 | (Apple did not document this feature, but it did publish the source code for all their Dovecot patches on the [Apple Open Source project site](http://www.opensource.apple.com/source/dovecot/dovecot-293/), which include this feature. So although I was not able to follow a specification, I was able to read their open source project and do a clean implementation with all original code.) 19 | 20 | Second, you need to install a daemon process, from the [dovecot-xaps-plugin](https://github.com/st3fan/dovecot-xaps-daemon) project, that will be responsible for receiving new email notifications from the Dovecot Local Delivery Agent or from the Dovecot LMTP server and transforming those into native Apple Push Notifications. 21 | 22 | Installation 23 | ============ 24 | 25 | Prerequisites 26 | ------------- 27 | 28 | You are going to need the following things to get this going: 29 | 30 | * Some patience and willingness to experiment - Although I run this project in production, it is still a very early version and it may contain bugs. 31 | * Because you will need a certificate to talk to the Apple Push Notifications Service, you can only run this software if you are migrating away from an existing OS X Server setup where you had Push Email enabled. How to export the certificate is described in the [dovecot-xaps-daemon project](https://github.com/st3fan/dovecot-xaps-daemon). 32 | * Dovecot > 2.2.19 (which introduced the push-notification plugin) 33 | 34 | > Note that you need to have an existing Dovecot setup working. Either with local system users or with virtual users. Also note that you need to be using the Dovecot Local Delivery Agent or the Dovecot LMTP server for this to work. The [Dovecot LDA](http://wiki2.dovecot.org/LDA) and the [LMTP server](http://wiki2.dovecot.org/LMTP) are described in detail on the Dovecot Wiki 35 | 36 | Installing the Dovecot plugins 37 | ------------------------------ 38 | 39 | First install the following Ubuntu 12.04.5 packages, or equivalent for your operating system. This list is longer than it should be because there is not yet a binary distribution for this project. 40 | 41 | ``` 42 | sudo apt-get build-dep dovecot-core 43 | sudo apt-get install git dovecot-dev cmake 44 | ``` 45 | 46 | Clone this project: 47 | 48 | ``` 49 | git clone https://github.com/st3fan/dovecot-xaps-plugin.git 50 | cd dovecot-xaps-plugin 51 | ``` 52 | 53 | Compile and install the plugins. Note that the installation destination in the `Makefile` is hardcoded for Ubuntu, it expects the Dovecot modules to live at `/usr/lib/dovecot/modules/`. You can either modify the `Makefile` or copy the modules to the right place manually. 54 | 55 | ``` 56 | mkdir build 57 | cd build 58 | cmake .. -DCMAKE_BUILD_TYPE=Release 59 | sudo make install 60 | ``` 61 | 62 | Install the configuration file. Also specific for Ubuntu, may be different for your operating system. 63 | 64 | ``` 65 | sudo cp xaps.conf /etc/dovecot/conf.d/95-xaps.conf 66 | ``` 67 | 68 | In the configuration file, change the `xaps_socket` option to point to the same location as you specified on the `xapsd` daemon arguments. 69 | 70 | Restart Dovecot: 71 | 72 | ``` 73 | sudo service dovecot restart 74 | ``` 75 | 76 | Debugging 77 | --------- 78 | 79 | Put a tail on `/var/log/mail.log` and keep an eye on the output of the `xapsd` daemon. (See instructions in that project). If you see any errors or core dumps, please [file a bug](https://github.com/st3fan/dovecot-xaps-plugin/issues/new). 80 | 81 | -------------------------------------------------------------------------------- /xaps-daemon.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Stefan Arentz 5 | * Copyright (c) 2017 Frederik Schwan 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #if (DOVECOT_VERSION_MAJOR > 2u || (DOVECOT_VERSION_MAJOR == 2u && DOVECOT_VERSION_MINOR >= 3u)) 30 | #include 31 | #include 32 | #endif 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "xaps-daemon.h" 41 | 42 | 43 | /* 44 | * Send the request to our daemon over a unix domain socket. The 45 | * protocol is very simple line based. We use an alarm to make sure 46 | * this request does not hang. 47 | */ 48 | int send_to_daemon(const char *socket_path, const string_t *payload, struct xaps_attr *xaps_attr) { 49 | int ret = -1; 50 | 51 | int fd = net_connect_unix(socket_path); 52 | if (fd == -1) { 53 | i_error("net_connect_unix(%s) failed: %m", socket_path); 54 | return -1; 55 | } 56 | 57 | net_set_nonblock(fd, FALSE); 58 | alarm(1); /* TODO: Should be a constant. What is a good duration? */ 59 | #ifdef OSTREAM_UNIX_H 60 | struct ostream *ostream = o_stream_create_unix(fd, (size_t)-1); 61 | o_stream_cork(ostream); 62 | o_stream_nsend(ostream, str_data(payload), str_len(payload)); 63 | o_stream_uncork(ostream); 64 | { 65 | if (o_stream_flush(ostream) < 1) { 66 | #else 67 | { 68 | if (net_transmit(fd, str_data(payload), str_len(payload)) < 0) { 69 | #endif 70 | i_error("write(%s) failed: %m", socket_path); 71 | ret = -1; 72 | } else { 73 | char res[1024]; 74 | ret = net_receive(fd, res, sizeof(res) - 1); 75 | if (ret < 0) { 76 | i_error("read(%s) failed: %m", socket_path); 77 | } else { 78 | res[ret] = '\0'; 79 | if (strncmp(res, "OK ", 3) == 0) { 80 | if (xaps_attr) { 81 | char *tmp; 82 | /* Remove whitespace the end. We expect \r\n. TODO: Looks shady. Is there a dovecot library function for this? */ 83 | str_append(xaps_attr->aps_topic, strtok_r(&res[3], "\r\n", &tmp)); 84 | } 85 | ret = 0; 86 | } 87 | } 88 | } 89 | } 90 | #ifdef OSTREAM_UNIX_H 91 | o_stream_destroy(&ostream); 92 | #endif 93 | alarm(0); 94 | 95 | net_disconnect(fd); 96 | return ret; 97 | } 98 | 99 | /** 100 | * Quote and escape a string. Not sure if this deals correctly with 101 | * unicode in mailbox names. 102 | */ 103 | 104 | static void xaps_str_append_quoted(string_t *dest, const char *str) { 105 | str_append_c(dest, '"'); 106 | str_append(dest, str_escape(str)); 107 | str_append_c(dest, '"'); 108 | } 109 | 110 | /** 111 | * Notify the backend daemon of an incoming mail. Right now we tell 112 | * the daemon the username and the mailbox in which a new email was 113 | * posted. The daemon can then lookup the user and see if any of the 114 | * devices want to receive a notification for that mailbox. 115 | */ 116 | 117 | int xaps_notify(const char *socket_path, const char *username, struct mail_user *mailuser , struct mailbox *mailbox, struct push_notification_txn_msg *msg) { 118 | struct push_notification_txn_event *const *event; 119 | /* 120 | * Construct the request. 121 | */ 122 | string_t *req = t_str_new(1024); 123 | str_append(req, "NOTIFY"); 124 | str_append(req, " dovecot-username="); 125 | xaps_str_append_quoted(req, username); 126 | str_append(req, "\tdovecot-mailbox="); 127 | xaps_str_append_quoted(req, mailbox->name); 128 | if (array_is_created(&msg->eventdata)) { 129 | str_append(req, "\tevents=("); 130 | int count = 0; 131 | array_foreach(&msg->eventdata, event) { 132 | if (count) { 133 | str_append(req, ","); 134 | } 135 | str_append(req, "\""); 136 | str_append(req, (*event)->event->event->name); 137 | str_append(req, "\""); 138 | count++; 139 | } 140 | str_append(req, ")"); 141 | 142 | } 143 | str_append(req, "\r\n"); 144 | 145 | 146 | push_notification_driver_debug(XAPS_LOG_LABEL, mailuser, "about to send: %p", req); 147 | return send_to_daemon(socket_path, req, NULL); 148 | } 149 | 150 | /** 151 | * Send a registration request to the daemon, which will do all the 152 | * hard work. 153 | */ 154 | int xaps_register(const char *socket_path, struct xaps_attr *xaps_attr) { 155 | /* 156 | * Construct our request. 157 | */ 158 | 159 | string_t *req = t_str_new(1024); 160 | str_append(req, "REGISTER"); 161 | str_append(req, " aps-account-id="); 162 | xaps_str_append_quoted(req, xaps_attr->aps_account_id); 163 | str_append(req, "\taps-device-token="); 164 | xaps_str_append_quoted(req, xaps_attr->aps_device_token); 165 | str_append(req, "\taps-subtopic="); 166 | xaps_str_append_quoted(req, xaps_attr->aps_subtopic); 167 | str_append(req, "\tdovecot-username="); 168 | xaps_str_append_quoted(req, xaps_attr->dovecot_username); 169 | str_append(req, ""); 170 | 171 | if (xaps_attr->mailboxes == NULL) { 172 | str_append(req, "\tdovecot-mailboxes=(\"INBOX\")"); 173 | } else { 174 | str_append(req, "\tdovecot-mailboxes=("); 175 | int next = 0; 176 | for (; !IMAP_ARG_IS_EOL(xaps_attr->mailboxes); xaps_attr->mailboxes++) { 177 | const char *mailbox; 178 | if (!imap_arg_get_astring(&(xaps_attr->mailboxes[0]), &mailbox)) { 179 | return -1; 180 | } 181 | if (next) { 182 | str_append(req, ","); 183 | } 184 | xaps_str_append_quoted(req, mailbox); 185 | next = 1; 186 | } 187 | str_append(req, ")"); 188 | } 189 | str_append(req, "\r\n"); 190 | 191 | return send_to_daemon(socket_path, req, xaps_attr); 192 | } 193 | -------------------------------------------------------------------------------- /xaps-daemon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Stefan Arentz 5 | * Copyright (c) 2017 Frederik Schwan 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #ifndef DOVECOT_XAPS_PLUGIN_XAPS_H 31 | #define DOVECOT_XAPS_PLUGIN_XAPS_H 32 | 33 | #define XAPS_LOG_LABEL "XAPS Push Notification: " 34 | #define DEFAULT_SOCKPATH "/var/run/dovecot/xapsd.sock" 35 | 36 | struct xaps_attr { 37 | const char *aps_version, *aps_account_id, *aps_device_token, *aps_subtopic; 38 | const struct imap_arg *mailboxes; 39 | const char *dovecot_username; 40 | string_t *aps_topic; 41 | }; 42 | 43 | int send_to_daemon(const char *socket_path, const string_t *payload, struct xaps_attr *xaps_attr); 44 | 45 | int xaps_notify(const char *socket_path, const char *username, struct mail_user *mailuser, struct mailbox *mailbox, struct push_notification_txn_msg *msg); 46 | 47 | int xaps_register(const char *socket_path, struct xaps_attr *xaps_attr); 48 | 49 | #endif -------------------------------------------------------------------------------- /xaps-imap-plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Stefan Arentz 5 | * Copyright (c) 2017 Frederik Schwan 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "xaps-imap-plugin.h" 32 | #include "xaps-daemon.h" 33 | 34 | const char *xapplepushservice_plugin_version = DOVECOT_ABI_VERSION; 35 | 36 | static struct module *xaps_imap_module; 37 | static imap_client_created_func_t *next_hook_client_created; 38 | 39 | /** 40 | * Command handler for the XAPPLEPUSHSERVICE command. The command is 41 | * used by iOS clients to register for push notifications. 42 | * 43 | * We receive a list of key value pairs from the client, with the 44 | * following keys: 45 | * 46 | * aps-version - always set to "2" 47 | * aps-account-id - a unique id the iOS device has associated with this account 48 | * aps-device-token - the APS device token 49 | * aps-subtopic - always set to "com.apple.mobilemail" 50 | * mailboxes - list of mailboxes to send notifications for 51 | * 52 | * For example: 53 | * 54 | * XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E 55 | * aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 56 | * aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes) 57 | * 58 | * To minimize the work that needs to be done inside the IMAP client, 59 | * we only parse and validate the parameters and then simply push all 60 | * of this to the supporting daemon that will record the mapping 61 | * between the account and the iOS client. 62 | */ 63 | static bool parse_xapplepush(struct client_command_context *cmd, struct xaps_attr *xaps_attr) { 64 | /* 65 | * Parse arguments. We expect four key value pairs. We only take 66 | * those that we understand for version 2 of this extension. 67 | */ 68 | 69 | const struct imap_arg *args; 70 | const char *arg_key, *arg_val; 71 | 72 | xaps_attr->dovecot_username = cmd->client->user->username; 73 | 74 | if (!client_read_args(cmd, 0, 0, &args)) { 75 | client_send_command_error(cmd, "Invalid arguments."); 76 | return FALSE; 77 | } 78 | 79 | for (int i = 0; i < 5; i++) { 80 | if (!imap_arg_get_astring(&args[i * 2 + 0], &arg_key)) { 81 | client_send_command_error(cmd, "Invalid arguments."); 82 | return FALSE; 83 | } 84 | 85 | // i=4 is a list with which imap_arg_get_astring segfaults 86 | if (i < 4 && !imap_arg_get_astring(&args[i * 2 + 1], &arg_val)) { 87 | client_send_command_error(cmd, "Invalid arguments."); 88 | return FALSE; 89 | } 90 | 91 | if (strcasecmp(arg_key, "aps-version") == 0) { 92 | xaps_attr->aps_version = arg_val; 93 | } else if (strcasecmp(arg_key, "aps-account-id") == 0) { 94 | xaps_attr->aps_account_id = arg_val; 95 | } else if (strcasecmp(arg_key, "aps-device-token") == 0) { 96 | xaps_attr->aps_device_token = arg_val; 97 | } else if (strcasecmp(arg_key, "aps-subtopic") == 0) { 98 | xaps_attr->aps_subtopic = arg_val; 99 | } else if (strcasecmp(arg_key, "mailboxes") == 0) { 100 | if (!imap_arg_get_list(&args[i * 2 + 1], &(xaps_attr->mailboxes))) { 101 | client_send_command_error(cmd, "Invalid arguments."); 102 | return FALSE; 103 | } 104 | } 105 | } 106 | 107 | /* 108 | * Check if this is a version we expect 109 | */ 110 | 111 | if (!xaps_attr->aps_version || strcmp(xaps_attr->aps_version, "2") != 0) { 112 | client_send_command_error(cmd, "Unknown aps-version."); 113 | return FALSE; 114 | } 115 | 116 | /* 117 | * Check if all of the parameters are there. 118 | */ 119 | 120 | if (!xaps_attr->aps_account_id || strlen(xaps_attr->aps_account_id) == 0) { 121 | client_send_command_error(cmd, "Incomplete or empty aps-account-id parameter."); 122 | return FALSE; 123 | } 124 | 125 | if (!xaps_attr->aps_device_token || strlen(xaps_attr->aps_device_token) == 0) { 126 | client_send_command_error(cmd, "Incomplete or empty aps-device-token parameter."); 127 | return FALSE; 128 | } 129 | 130 | if (!xaps_attr->aps_subtopic || strlen(xaps_attr->aps_subtopic) == 0) { 131 | client_send_command_error(cmd, "Incomplete or empty aps-subtopic parameter."); 132 | return FALSE; 133 | } 134 | 135 | if(!xaps_attr->mailboxes) { 136 | client_send_command_error(cmd, "Incomplete or empty mailboxes parameter."); 137 | return FALSE; 138 | } 139 | 140 | return TRUE; 141 | } 142 | 143 | /* 144 | * Register the client at the xapsd 145 | */ 146 | static bool register_client(struct client_command_context *cmd, struct xaps_attr *xaps_attr) { 147 | /* 148 | * Forward to the helper daemon. The helper will return the 149 | * aps-topic, which in reality is the subject of the certificate. 150 | */ 151 | xaps_attr->aps_topic = t_str_new(0); 152 | 153 | if (xaps_register(socket_path, xaps_attr) != 0) { 154 | client_send_command_error(cmd, "Registration failed."); 155 | return FALSE; 156 | } 157 | 158 | /* 159 | * Return success. We assume that aps_version and aps_topic do not 160 | * contain anything that needs to be escaped. 161 | */ 162 | 163 | client_send_line(cmd->client, 164 | t_strdup_printf("* XAPPLEPUSHSERVICE aps-version \"%s\" aps-topic \"%s\"", xaps_attr->aps_version, 165 | str_c(xaps_attr->aps_topic))); 166 | client_send_tagline(cmd, "OK XAPPLEPUSHSERVICE Registration successful."); 167 | return TRUE; 168 | } 169 | 170 | /* 171 | * Handle any XAPPLEPUSHSERVICE command 172 | */ 173 | static bool cmd_xapplepushservice(struct client_command_context *cmd) { 174 | struct xaps_attr xaps_attr; 175 | 176 | if (!parse_xapplepush(cmd, &xaps_attr)) { 177 | return FALSE; 178 | } 179 | if (!register_client(cmd, &xaps_attr)) { 180 | return FALSE; 181 | } 182 | return TRUE; 183 | } 184 | 185 | /** 186 | * This hook is called when a client has connected but before the 187 | * capability string has been sent. We simply add XAPPLEPUSHSERVICE to 188 | * the capabilities. This will trigger the usage of the 189 | * XAPPLEPUSHSERVICE command by iOS clients. 190 | */ 191 | 192 | static void xaps_client_created(struct client **client) { 193 | if (mail_user_is_plugin_loaded((*client)->user, xaps_imap_module)) { 194 | str_append((*client)->capability_string, " XAPPLEPUSHSERVICE"); 195 | } 196 | socket_path = mail_user_plugin_getenv((*client)->user, "xaps_socket"); 197 | if (socket_path == NULL) { 198 | socket_path = DEFAULT_SOCKPATH; 199 | } 200 | 201 | if (next_hook_client_created != NULL) { 202 | next_hook_client_created(client); 203 | } 204 | } 205 | 206 | 207 | /** 208 | * This plugin method is called when the plugin is globally 209 | * initialized. We register a new command, XAPPLEPUSHSERVICE, and also 210 | * setup the client_created hook so that we can modify the 211 | * capabilities string. 212 | */ 213 | 214 | void xaps_imap_plugin_init(struct module *module) { 215 | command_register("XAPPLEPUSHSERVICE", cmd_xapplepushservice, 0); 216 | 217 | xaps_imap_module = module; 218 | next_hook_client_created = imap_client_created_hook_set(xaps_client_created); 219 | } 220 | 221 | 222 | /** 223 | * This plugin method is called when the plugin is globally 224 | * deinitialized. We unregister our command and remove the 225 | * client_created hook. 226 | */ 227 | 228 | void xaps_imap_plugin_deinit(void) { 229 | imap_client_created_hook_set(next_hook_client_created); 230 | 231 | command_unregister("XAPPLEPUSHSERVICE"); 232 | } 233 | 234 | 235 | /** 236 | * This plugin only makes sense in the context of IMAP. 237 | */ 238 | 239 | const char xaps_imap_plugin_binary_dependency[] = "imap"; 240 | -------------------------------------------------------------------------------- /xaps-imap-plugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Stefan Arentz 5 | * Copyright (c) 2017 Frederik Schwan 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | #ifndef XAPS_IMAP_PLUGIN_H 27 | #define XAPS_IMAP_PLUGIN_H 28 | 29 | struct module; 30 | 31 | extern const char xaps_imap_plugin_binary_dependency[]; 32 | const char *socket_path; 33 | 34 | void xaps_imap_plugin_init(struct module *module); 35 | 36 | void xaps_imap_plugin_deinit(void); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /xaps-push-notification-plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Stefan Arentz 5 | * Copyright (c) 2017 Frederik Schwan 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include "xaps-push-notification-plugin.h" 39 | #include "xaps-daemon.h" 40 | 41 | const char *xaps_plugin_version = DOVECOT_ABI_VERSION; 42 | 43 | /* 44 | * Prepare message handling. 45 | * On return of false, the event gets dismissed for this driver 46 | */ 47 | static bool xaps_plugin_begin_txn(struct push_notification_driver_txn *dtxn) { 48 | const struct push_notification_event *const *event; 49 | struct push_notification_event_messagenew_config *eventMessagenewConfig; 50 | struct push_notification_event_messageappend_config *eventMessageappendConfig; 51 | 52 | push_notification_driver_debug(XAPS_LOG_LABEL, dtxn->ptxn->muser, "begin_txn: user: %s mailbox: %s", 53 | dtxn->ptxn->muser->username, dtxn->ptxn->mbox->name); 54 | 55 | // we have to initialize each event 56 | // the MessageNew event needs a config to appear in the process_msg function 57 | // so it's handled separately 58 | array_foreach(&push_notification_events, event) { 59 | if (strcmp((*event)->name,"MessageNew") == 0) { 60 | eventMessagenewConfig = p_new(dtxn->ptxn->pool, struct push_notification_event_messagenew_config, 1); 61 | // Take what you can, give nothing back 62 | eventMessagenewConfig->flags = PUSH_NOTIFICATION_MESSAGE_HDR_DATE | 63 | PUSH_NOTIFICATION_MESSAGE_HDR_FROM | 64 | PUSH_NOTIFICATION_MESSAGE_HDR_TO | 65 | PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT | 66 | PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET; 67 | push_notification_event_init(dtxn, "MessageNew", eventMessagenewConfig); 68 | } else if (strcmp((*event)->name,"MessageAppend") == 0) { 69 | eventMessageappendConfig = p_new(dtxn->ptxn->pool, struct push_notification_event_messageappend_config, 1); 70 | // Take what you can, give nothing back 71 | eventMessageappendConfig->flags = PUSH_NOTIFICATION_MESSAGE_HDR_DATE | 72 | PUSH_NOTIFICATION_MESSAGE_HDR_FROM | 73 | PUSH_NOTIFICATION_MESSAGE_HDR_TO | 74 | PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT | 75 | PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET; 76 | push_notification_event_init(dtxn, "MessageAppend", eventMessageappendConfig); 77 | } else { 78 | push_notification_event_init(dtxn, (*event)->name, NULL); 79 | } 80 | } 81 | return TRUE; 82 | } 83 | 84 | /* 85 | * Process the actual message 86 | */ 87 | static void xaps_plugin_process_msg(struct push_notification_driver_txn *dtxn, struct push_notification_txn_msg *msg) { 88 | struct push_notification_txn_event *const *event; 89 | 90 | if (array_is_created(&msg->eventdata)) { 91 | array_foreach(&msg->eventdata, event) { 92 | push_notification_driver_debug(XAPS_LOG_LABEL, dtxn->ptxn->muser, 93 | "Handling event: %s", (*event)->event->event->name); 94 | } 95 | } 96 | const char *username = dtxn->ptxn->muser->username; 97 | if (user_lookup != NULL) { 98 | username = mail_user_plugin_getenv(dtxn->ptxn->muser, user_lookup); 99 | } 100 | if (xaps_notify(socket_path, username, dtxn->ptxn->muser, dtxn->ptxn->mbox, msg) != 0) { 101 | i_error("cannot notify"); 102 | } 103 | } 104 | 105 | // push-notification driver definition 106 | 107 | const char *xaps_plugin_dependencies[] = { "push_notification", NULL }; 108 | 109 | extern struct push_notification_driver push_notification_driver_xaps; 110 | 111 | int xaps_plugin_init(struct push_notification_driver_config *dconfig ATTR_UNUSED, 112 | struct mail_user *muser, 113 | pool_t pPool ATTR_UNUSED, 114 | void **pVoid ATTR_UNUSED, 115 | const char **pString ATTR_UNUSED) { 116 | socket_path = mail_user_plugin_getenv(muser, "xaps_socket"); 117 | if (socket_path == NULL) { 118 | socket_path = DEFAULT_SOCKPATH; 119 | } 120 | user_lookup = mail_user_plugin_getenv(muser, "xaps_user_lookup"); 121 | return 0; 122 | } 123 | 124 | void xaps_plugin_deinit(struct push_notification_driver_user *duser ATTR_UNUSED) { 125 | } 126 | 127 | struct push_notification_driver push_notification_driver_xaps = { 128 | .name = "xaps", 129 | .v = { 130 | .init = xaps_plugin_init, 131 | .begin_txn = xaps_plugin_begin_txn, 132 | .process_msg = xaps_plugin_process_msg, 133 | .deinit = xaps_plugin_deinit, 134 | } 135 | }; 136 | 137 | // plugin init and deinit 138 | 139 | void xaps_push_notification_plugin_init(struct module *module ATTR_UNUSED) { 140 | push_notification_driver_register(&push_notification_driver_xaps); 141 | } 142 | 143 | void xaps_push_notification_plugin_deinit(void) { 144 | push_notification_driver_unregister(&push_notification_driver_xaps); 145 | } -------------------------------------------------------------------------------- /xaps-push-notification-plugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Stefan Arentz 5 | * Copyright (c) 2017 Frederik Schwan 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | #ifndef XAPS_PUSH_NOTIFICATION_PLUGIN_H 27 | #define XAPS_PUSH_NOTIFICATION_PLUGIN_H 28 | 29 | struct module; 30 | 31 | extern const char *xaps_plugin_dependencies[]; 32 | const char *socket_path; 33 | const char *user_lookup; 34 | 35 | void xaps_push_notification_plugin_init(struct module *module); 36 | void xaps_push_notification_plugin_deinit(void); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /xaps.conf: -------------------------------------------------------------------------------- 1 | protocol imap { 2 | mail_plugins = $mail_plugins notify push_notification xaps_push_notification xaps_imap 3 | } 4 | 5 | protocol lda { 6 | mail_plugins = $mail_plugins notify push_notification xaps_push_notification 7 | } 8 | 9 | protocol lmtp { 10 | mail_plugins = $mail_plugins notify push_notification xaps_push_notification 11 | } 12 | 13 | plugin { 14 | # Defaults to /var/run/dovecot/xapsd.sock 15 | #xaps_socket = 16 | # Defaults to NULL. Use if you want to determine the username used for PNs from environment variables provided by 17 | # login mechanism. Value is variable name to look up. 18 | #xaps_user_lookup = 19 | push_notification_driver = xaps 20 | } 21 | 22 | --------------------------------------------------------------------------------