From 4e96db26ad09aecdd4a80ddb6a1723e06437fa8a Mon Sep 17 00:00:00 2001 From: titanism <101466223+titanism@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:45:22 -0500 Subject: [PATCH] feat: added base support for XAPPLEPUSHSERVICE (per #711) (#712) * feat: added XAPPLEPUSHSERVICE support (per #711) * fix: added missing XAPPLEPUSHSERVICE capability advertisement * fix: fixed parsing of attributes * fix: invoke `this.send` with successful registration response to `XAPPLEPUSHSERVICE` command We were missing sending a response with the version and sub topic as seen in these references: --- imap-core/lib/commands/xapplepushservice.js | 149 ++++++++++++++++++++ imap-core/lib/imap-command.js | 3 +- imap-core/lib/imap-tools.js | 3 + imap.js | 2 + lib/handlers/on-xapplepushservice.js | 23 +++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 imap-core/lib/commands/xapplepushservice.js create mode 100644 lib/handlers/on-xapplepushservice.js diff --git a/imap-core/lib/commands/xapplepushservice.js b/imap-core/lib/commands/xapplepushservice.js new file mode 100644 index 00000000..158db1cf --- /dev/null +++ b/imap-core/lib/commands/xapplepushservice.js @@ -0,0 +1,149 @@ +'use strict'; + +// +// Thanks to Forward Email +// +// +// tag XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes) +// + +module.exports = { + state: ['Authenticated', 'Selected'], + + /* + Schema: [ + { + name: 'aps-version', + type: 'number' // always 2 + }, + { + name: 'aps-account-id', + type: 'string' + }, + { + name: 'aps-device-token', + type: 'string' + }, + { + name: 'aps-subtopic', + type: 'string' // always "com.apple.mobilemail" + }, + // NOTE: this is irrelevant as it won't be used until we figure out how to notify for other than INBOX + // + { + name: 'mailboxes', + type: 'string' // e.g. (INBOX Notes) + } + ], + */ + + // it's actually something like this in production + // [ + // { type: 'ATOM', value: 'aps-version' }, + // { type: 'ATOM', value: '2' }, + // { type: 'ATOM', value: 'aps-account-id' }, + // { type: 'ATOM', value: 'xxxxxxx' }, + // { type: 'ATOM', value: 'aps-device-token' }, + // { + // type: 'ATOM', + // value: 'xxxxxx' + // }, + // { type: 'ATOM', value: 'aps-subtopic' }, + // { type: 'ATOM', value: 'com.apple.mobilemail' }, + // { type: 'ATOM', value: 'mailboxes' }, + // [ + // { type: 'STRING', value: 'Sent Mail' }, + // { type: 'STRING', value: 'INBOX' } + // ] + // ] + + // disabled for now + schema: false, + + handler(command, callback) { + // Command = { + // tag: 'I5', + // command: 'XAPPLEPUSHSERVICE', + // attributes: [ + // { type: 'ATOM', value: 'aps-version' }, // 0 + // { type: 'ATOM', value: '2' }, // 1 + // { type: 'ATOM', value: 'aps-account-id' }, // 2 + // { type: 'ATOM', value: 'xxxxxx' }, // 3 + // { type: 'ATOM', value: 'aps-device-token' }, // 4 + // { // 5 + // type: 'ATOM', + // value: 'xxxxxx' + // }, + // { type: 'ATOM', value: 'aps-subtopic' }, // 6 + // { type: 'ATOM', value: 'com.apple.mobilemail' }, // 7 + // { type: 'ATOM', value: 'mailboxes' }, // 8 + // [ // 9 + // { type: 'STRING', value: 'Sent Mail' }, + // { type: 'STRING', value: 'INBOX' } + // ] + // ] + // } + + const version = (command.attributes[1] && command.attributes[1].value) || ''; + if (version !== '2') { + return callback(null, { + response: 'NO', + code: 'CLIENTBUG', + }); + } + + const accountID = (command.attributes[3] && command.attributes[3].value) || ''; + const deviceToken = (command.attributes[5] && command.attributes[5].value) || ''; + const subTopic = (command.attributes[7] && command.attributes[7].value) || ''; + + if (subTopic !== 'com.apple.mobilemail') { + return callback(null, { + response: 'NO', + code: 'CLIENTBUG', + }); + } + + // NOTE: mailboxes param is not used at this time (it's a list anyways too) + const mailboxes = command.attributes[9] && Array.isArray(command.attributes[9]) && command.attributes[9].length > 0 ? command.attributes[9].map(object => object.value) : []; + + if (typeof this._server.onXAPPLEPUSHSERVICE !== 'function') { + return callback(null, { + response: 'NO', + message: command.command + ' not implemented', + }); + } + + const logdata = { + short_message: '[XAPPLEPUSHSERVICE]', + _mail_action: 'xapplepushservice', + _accountId: accountID, + _deviceToken: deviceToken, + _subTopic: subTopic, + _mailboxes: mailboxes, + _user: this.session.user.id.toString(), + _sess: this.id, + }; + + this._server.onXAPPLEPUSHSERVICE(accountID, deviceToken, subTopic, mailboxes, this.session, error => { + if (error) { + logdata._error = error.message; + logdata._code = error.code; + logdata._response = error.response; + this._server.loggelf(logdata); + + return callback(null, { + response: 'NO', + code: 'TEMPFAIL', + }); + } + + // + // + this.send(`* XAPPLEPUSHSERVICE aps-version "${version}" aps-topic "${subTopic}"`); + callback(null, { + response: 'OK', + message: 'XAPPLEPUSHSERVICE Registration successful.' + }); + }); + }, +}; diff --git a/imap-core/lib/imap-command.js b/imap-core/lib/imap-command.js index 1da07997..14f21f75 100644 --- a/imap-core/lib/imap-command.js +++ b/imap-core/lib/imap-command.js @@ -49,7 +49,8 @@ const commands = new Map([ ['GETQUOTAROOT', require('./commands/getquotaroot')], ['SETQUOTA', require('./commands/setquota')], ['GETQUOTA', require('./commands/getquota')], - ['COMPRESS', require('./commands/compress')] + ['COMPRESS', require('./commands/compress')], + ['XAPPLEPUSHSERVICE', require('./commands/xapplepushservice')] /*eslint-enable global-require*/ ]); diff --git a/imap-core/lib/imap-tools.js b/imap-core/lib/imap-tools.js index 196667f4..dfe284a1 100644 --- a/imap-core/lib/imap-tools.js +++ b/imap-core/lib/imap-tools.js @@ -717,6 +717,9 @@ module.exports.getQueryResponse = function (query, message, options) { module.exports.sendCapabilityResponse = connection => { let capabilities = []; + if (typeof connection._server.onXAPPLEPUSHSERVICE === 'function') + capabilities.push('XAPPLEPUSHSERVICE'); + if (!connection.secure) { if (!connection._server.options.disableSTARTTLS) { capabilities.push('STARTTLS'); diff --git a/imap.js b/imap.js index 34744ff1..9a862290 100644 --- a/imap.js +++ b/imap.js @@ -36,6 +36,7 @@ const onMove = require('./lib/handlers/on-move'); const onSearch = require('./lib/handlers/on-search'); const onGetQuotaRoot = require('./lib/handlers/on-get-quota-root'); const onGetQuota = require('./lib/handlers/on-get-quota'); +// const onXAPPLEPUSHSERVICE = require('./lib/handlers/on-xapplepushservice'); let logger = { info(...args) { @@ -156,6 +157,7 @@ let createInterface = (ifaceOptions, callback) => { server.onSearch = onSearch(server); server.onGetQuotaRoot = onGetQuotaRoot(server); server.onGetQuota = onGetQuota(server); + // server.onXAPPLEPUSHSERVICE = onXAPPLEPUSHSERVICE(server); if (loggelf) { server.loggelf = loggelf; diff --git a/lib/handlers/on-xapplepushservice.js b/lib/handlers/on-xapplepushservice.js new file mode 100644 index 00000000..80d88832 --- /dev/null +++ b/lib/handlers/on-xapplepushservice.js @@ -0,0 +1,23 @@ +'use strict'; + +// +// Thanks to Forward Email +// +// +// tag XAPPLEPUSHSERVICE aps-version 2 aps-account-id 0715A26B-CA09-4730-A419-793000CA982E aps-device-token 2918390218931890821908309283098109381029309829018310983092892829 aps-subtopic com.apple.mobilemail mailboxes (INBOX Notes) +// +module.exports = server => (accountID, deviceToken, subTopic, mailboxes, session, callback) => { + server.logger.debug( + { + tnx: 'xapplepushservice', + cid: session.id + }, + '[%s] XAPPLEPUSHSERVICE accountID "%s" deviceToken "%s" subTopic "%s" mailboxes "%s"', + session.id, + accountID, + deviceToken, + subTopic, + mailboxes + ); + return callback(new Error('Not implemented, see ')); +};