├── .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 |
4 |
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 |
--------------------------------------------------------------------------------