Updated XAPPLEPUSHSERVICE schema handling

This commit is contained in:
Andris Reinman 2024-07-29 11:37:30 +03:00
parent 4e96db26ad
commit 4ef3eaabca
No known key found for this signature in database
GPG key ID: DC6C83F4D584D364
5 changed files with 178 additions and 128 deletions

View file

@ -54,6 +54,11 @@ ignoredHosts = []
#secure=false #secure=false
#ignoreSTARTTLS=true #ignoreSTARTTLS=true
# Apple push notificiations
# TODO: missing actual implementation for Apple Push Service
[aps]
enabled = false
[setup] [setup]
# Public configuration for IMAP # Public configuration for IMAP
hostname = "localhost" hostname = "localhost"

View file

@ -7,143 +7,178 @@
// 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) // 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)
// //
const requiredKeys = ['aps-version', 'aps-account-id', 'aps-device-token', 'aps-subtopic', 'mailboxes'];
module.exports = { module.exports = {
state: ['Authenticated', 'Selected'], state: ['Authenticated', 'Selected'],
/* // the input is a key-value set which is not supported by the default schema handler
Schema: [ schema: false,
{
name: 'aps-version', // [
type: 'number' // always 2 // { type: 'ATOM', value: 'aps-version' },
}, // { type: 'ATOM', value: '2' },
{ // { type: 'ATOM', value: 'aps-account-id' },
name: 'aps-account-id', // { type: 'ATOM', value: 'xxxxxxx' },
type: 'string' // { type: 'ATOM', value: 'aps-device-token' },
}, // {
{ // type: 'ATOM',
name: 'aps-device-token', // value: 'xxxxxx'
type: 'string' // },
}, // { type: 'ATOM', value: 'aps-subtopic' },
{ // { type: 'ATOM', value: 'com.apple.mobilemail' },
name: 'aps-subtopic', // { type: 'ATOM', value: 'mailboxes' },
type: 'string' // always "com.apple.mobilemail" // [
}, // { type: 'STRING', value: 'Sent Mail' },
// NOTE: this is irrelevant as it won't be used until we figure out how to notify for other than INBOX // { type: 'STRING', value: 'INBOX' }
// <https://github.com/nodemailer/wildduck/issues/711#issuecomment-2251643672> // ]
{ // ]
name: 'mailboxes',
type: 'string' // e.g. (INBOX Notes) 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 apsConfig = this._server.options.aps || {};
// Reject if not enabled
if (!apsConfig.enabled) {
return callback(null, {
response: 'BAD',
message: `Unknown command: ${command.command}`
});
} }
],
*/
// it's actually something like this in production // Parse input arguments into a structured object:
// [
// { 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, // "aps-version": "2",
// "aps-account-id": "0715A26B-CA09-4730-A419-793000CA982E",
// "aps-device-token": "2918390218931890821908309283098109381029309829018310983092892829",
// "aps-subtopic": "com.apple.mobilemail",
// "mailboxes": [
// "INBOX",
// "Notes"
// ]
// }
handler(command, callback) { let data = {};
// Command = { let keyName;
// tag: 'I5', for (let i = 0, len = (command.attributes || []).length; i < len; i++) {
// command: 'XAPPLEPUSHSERVICE', let isKey = i % 2 === 0;
// attributes: [ let attr = command.attributes[i];
// { type: 'ATOM', value: 'aps-version' }, // 0 if (isKey && !['ATOM', 'STRING'].includes(attr.type)) {
// { type: 'ATOM', value: '2' }, // 1 return callback(null, {
// { type: 'ATOM', value: 'aps-account-id' }, // 2 response: 'BAD',
// { type: 'ATOM', value: 'xxxxxx' }, // 3 message: `Invalid argument for ${command.command}`
// { type: 'ATOM', value: 'aps-device-token' }, // 4 });
// { // 5 }
// type: 'ATOM', if (isKey) {
// value: 'xxxxxx' keyName = (attr.value || '').toString().toLowerCase();
// }, continue;
// { 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 (!requiredKeys.includes(keyName)) {
if (version !== '2') { // skip unknown keys
return callback(null, { }
response: 'NO',
code: 'CLIENTBUG',
});
}
const accountID = (command.attributes[3] && command.attributes[3].value) || ''; if (['ATOM', 'STRING'].includes(attr.type)) {
const deviceToken = (command.attributes[5] && command.attributes[5].value) || ''; data[keyName] = (attr.value || '').toString();
const subTopic = (command.attributes[7] && command.attributes[7].value) || ''; } else if (Array.isArray(attr) && keyName === 'mailboxes') {
let mailboxes = attr
.map(entry => {
if (['ATOM', 'STRING'].includes(entry.type)) {
return (entry.value || '').toString();
}
return false;
})
.filter(name => name);
data[keyName] = mailboxes;
}
}
if (subTopic !== 'com.apple.mobilemail') { // Make sure all required keys (exept mailboxes) are present
return callback(null, { for (let requiredKey of requiredKeys) {
response: 'NO', if (!data[requiredKey] && requiredKey !== 'mailboxes') {
code: 'CLIENTBUG', return callback(null, {
}); response: 'BAD',
} message: `Missing required arguments for ${command.command}`
});
}
}
// NOTE: mailboxes param is not used at this time (it's a list anyways too) const version = data['aps-version'];
const mailboxes = command.attributes[9] && Array.isArray(command.attributes[9]) && command.attributes[9].length > 0 ? command.attributes[9].map(object => object.value) : []; const accountID = data['aps-account-id'];
const deviceToken = data['aps-device-token'];
const subTopic = data['aps-subtopic'];
const mailboxes = data.mailboxes || [];
if (typeof this._server.onXAPPLEPUSHSERVICE !== 'function') { if (version !== '2') {
return callback(null, { return callback(null, {
response: 'NO', response: 'NO',
message: command.command + ' not implemented', message: 'Unsupported APS version',
}); code: 'CLIENTBUG'
} });
}
const logdata = { if (subTopic !== 'com.apple.mobilemail') {
short_message: '[XAPPLEPUSHSERVICE]', return callback(null, {
_mail_action: 'xapplepushservice', response: 'NO',
_accountId: accountID, message: `Invalid subtopic for ${command.command}`,
_deviceToken: deviceToken, code: 'CLIENTBUG'
_subTopic: subTopic, });
_mailboxes: mailboxes, }
_user: this.session.user.id.toString(),
_sess: this.id,
};
this._server.onXAPPLEPUSHSERVICE(accountID, deviceToken, subTopic, mailboxes, this.session, error => { const logdata = {
if (error) { short_message: '[XAPPLEPUSHSERVICE]',
logdata._error = error.message; _mail_action: 'xapplepushservice',
logdata._code = error.code; _accountId: accountID,
logdata._response = error.response; _deviceToken: deviceToken,
this._server.loggelf(logdata); _subTopic: subTopic,
_mailboxes: mailboxes,
_user: this.session.user.id.toString(),
_sess: this.id
};
return callback(null, { this._server.onXAPPLEPUSHSERVICE(accountID, deviceToken, subTopic, mailboxes, this.session, error => {
response: 'NO', if (error) {
code: 'TEMPFAIL', logdata._error = error.message;
}); logdata._code = error.code;
} logdata._response = error.response;
this._server.loggelf(logdata);
// <https://opensource.apple.com/source/dovecot/dovecot-293/dovecot/src/imap/cmd-x-apple-push-service.c.auto.html> return callback(null, {
// <https://github.com/st3fan/dovecot-xaps-plugin/blob/3d1c71e0c78cc35ca6ead21f49a8e0e35e948a7c/xaps-imap-plugin.c#L158-L166> response: 'NO',
this.send(`* XAPPLEPUSHSERVICE aps-version "${version}" aps-topic "${subTopic}"`); code: 'TEMPFAIL'
callback(null, { });
response: 'OK', }
message: 'XAPPLEPUSHSERVICE Registration successful.'
}); // <https://opensource.apple.com/source/dovecot/dovecot-293/dovecot/src/imap/cmd-x-apple-push-service.c.auto.html>
}); // <https://github.com/st3fan/dovecot-xaps-plugin/blob/3d1c71e0c78cc35ca6ead21f49a8e0e35e948a7c/xaps-imap-plugin.c#L158-L166>
}, this.send(`* XAPPLEPUSHSERVICE aps-version "${version}" aps-topic "${subTopic}"`);
callback(null, {
response: 'OK',
message: 'XAPPLEPUSHSERVICE Registration successful.'
});
});
}
}; };

View file

@ -717,9 +717,6 @@ module.exports.getQueryResponse = function (query, message, options) {
module.exports.sendCapabilityResponse = connection => { module.exports.sendCapabilityResponse = connection => {
let capabilities = []; let capabilities = [];
if (typeof connection._server.onXAPPLEPUSHSERVICE === 'function')
capabilities.push('XAPPLEPUSHSERVICE');
if (!connection.secure) { if (!connection.secure) {
if (!connection._server.options.disableSTARTTLS) { if (!connection._server.options.disableSTARTTLS) {
capabilities.push('STARTTLS'); capabilities.push('STARTTLS');
@ -766,6 +763,10 @@ module.exports.sendCapabilityResponse = connection => {
if (connection._server.options.maxMessage) { if (connection._server.options.maxMessage) {
capabilities.push('APPENDLIMIT=' + connection._server.options.maxMessage); capabilities.push('APPENDLIMIT=' + connection._server.options.maxMessage);
} }
if (connection._server.options.aps?.enabled) {
capabilities.push('XAPPLEPUSHSERVICE');
}
} }
capabilities.sort((a, b) => a.localeCompare(b)); capabilities.sort((a, b) => a.localeCompare(b));

View file

@ -36,7 +36,7 @@ const onMove = require('./lib/handlers/on-move');
const onSearch = require('./lib/handlers/on-search'); const onSearch = require('./lib/handlers/on-search');
const onGetQuotaRoot = require('./lib/handlers/on-get-quota-root'); const onGetQuotaRoot = require('./lib/handlers/on-get-quota-root');
const onGetQuota = require('./lib/handlers/on-get-quota'); const onGetQuota = require('./lib/handlers/on-get-quota');
// const onXAPPLEPUSHSERVICE = require('./lib/handlers/on-xapplepushservice'); const onXAPPLEPUSHSERVICE = require('./lib/handlers/on-xapplepushservice');
let logger = { let logger = {
info(...args) { info(...args) {
@ -78,6 +78,8 @@ let createInterface = (ifaceOptions, callback) => {
vendor: config.imap.vendor || 'Kreata' vendor: config.imap.vendor || 'Kreata'
}, },
aps: config.imap.aps,
logger, logger,
maxMessage: config.imap.maxMB * 1024 * 1024, maxMessage: config.imap.maxMB * 1024 * 1024,
@ -157,7 +159,7 @@ let createInterface = (ifaceOptions, callback) => {
server.onSearch = onSearch(server); server.onSearch = onSearch(server);
server.onGetQuotaRoot = onGetQuotaRoot(server); server.onGetQuotaRoot = onGetQuotaRoot(server);
server.onGetQuota = onGetQuota(server); server.onGetQuota = onGetQuota(server);
// server.onXAPPLEPUSHSERVICE = onXAPPLEPUSHSERVICE(server); server.onXAPPLEPUSHSERVICE = onXAPPLEPUSHSERVICE(server);
if (loggelf) { if (loggelf) {
server.loggelf = loggelf; server.loggelf = loggelf;

View file

@ -6,6 +6,12 @@
// <https://github.com/nodemailer/wildduck/issues/711> // <https://github.com/nodemailer/wildduck/issues/711>
// 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) // 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)
// //
// TODO:
// 1. store APS information in DB, each deviceToken separately
// 2. on new email use the stored information to push to apple (use mathcing deviceTokens as an array of recipients)
// 3. if pushing to a specific deviceToken yields in 410, remove that token
module.exports = server => (accountID, deviceToken, subTopic, mailboxes, session, callback) => { module.exports = server => (accountID, deviceToken, subTopic, mailboxes, session, callback) => {
server.logger.debug( server.logger.debug(
{ {
@ -19,5 +25,6 @@ module.exports = server => (accountID, deviceToken, subTopic, mailboxes, session
subTopic, subTopic,
mailboxes mailboxes
); );
return callback(new Error('Not implemented, see <https://github.com/nodemailer/wildduck/issues/711>')); return callback(new Error('Not implemented, see <https://github.com/nodemailer/wildduck/issues/711>'));
}; };