Mercurial > psgxs
view psgxs.js @ 15:60c80751cfa5
JID handling conforming to the RFC.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Tue, 19 Oct 2010 17:48:45 +0200 |
parents | 9a6b8b3357c6 |
children | ec7cea92fe8a |
line wrap: on
line source
#!/usr/bin/env node /* * Copyright (C) 2010 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> * * This file is part of PSĜS, a PubSub server written in JavaScript. * * PSĜS is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License. * * PSĜS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with PSĜS. If not, see <http://www.gnu.org/licenses/>. */ var xmpp = require('xmpp'); var sha1 = require('sha1'); require('./iso8601'); var storage = require('./storage'); var errors = require('./errors'); var utils = require('./util'); var toBareJID = utils.toBareJID; var config = require('./configuration'); var forms = require('./forms'); var conn = new xmpp.Connection(); var service_configuration = config.service_configuration; var componentJID = config.jid; var componentPassword = config.password; conn.log = function (_, m) { console.log(m); }; function _(obj, color) { var str = require('sys').inspect(obj, false, null); if (color) console.log('\033['+c+';1m' + str + '\033[0m'); else console.log(str); }; process.addListener('uncaughtException', function (err) { console.log('\033[41;1mUncaught exception, this should never happen: ' + err + '.\033[0m'); }); if (typeof xmpp.StanzaBuilder.cnode != 'function' || typeof xmpp.StanzaBuilder.prototype.cnode != 'function') { xmpp.StanzaBuilder.prototype.cnode = function (stanza) { var parent = this; parent.tags.push(stanza); parent.children.push(stanza); return this; }; } function onIq(stanza) { var type = stanza.getAttribute('type'); var from = stanza.getAttribute('to'); var to = stanza.getAttribute('from'); var id = stanza.getAttribute('id'); var response; if (id) response = xmpp.iq({to: to, from: from, type: 'result', id: id}); else response = xmpp.iq({to: to, from: from, type: 'result'}); if (type == 'get') { // XEP-0092: Software Version if (stanza.getChild('query', 'jabber:iq:version')) { var query = xmpp.stanza('query', {xmlns: 'jabber:iq:version'}) .s('name').t('PSĜS') .s('version').t(config.version) .s('os').t(config.os); response.cnode(query); // SECTION 5.1 } else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#info')) { var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#info'); var nodeID = query.getAttribute('node'); // SECTION 5.3 if (nodeID && nodeID != '') { if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var conf = storage.getConfiguration(nodeID); if (typeof conf == 'number') return makeError(response, conf); var type = 'leaf' if (conf['pubsub#node_type']) type = conf['pubsub#node_type']; var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info', node: nodeID}) q.s('identity', {category: 'pubsub', type: type}); q.s('feature', {'var': 'http://jabber.org/protocol/pubsub'}); // SECTION 5.4 if (config.enabled('meta-data')) { var x = forms.build('result', 'node_metadata', storage.getMetadata(nodeID), true); if (x) q.cnode(x); } response.cnode(q); // SECTION 5.1 } else { var q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#info'}) .s('identity', {category: 'pubsub', type: 'service', name: 'PubSub JavaScript Server'}) .s('feature', {'var': 'http://jabber.org/protocol/disco#info'}) .s('feature', {'var': 'http://jabber.org/protocol/disco#items'}) .s('feature', {'var': 'http://jabber.org/protocol/pubsub'}) .s('feature', {'var': 'jabber:iq:version'}) .s('feature', {'var': 'http://jabber.org/protocol/commands'}); for (var i in config.activated) if (typeof i == 'string') q.s('feature', {'var': 'http://jabber.org/protocol/pubsub#' + config.activated[i]}); response.cnode(q); } // SECTION 5.2 } else if (stanza.getChild('query', 'http://jabber.org/protocol/disco#items')) { var query = stanza.getChild('query', 'http://jabber.org/protocol/disco#items'); var q; var children; var nodeID = query.getAttribute('node'); if (nodeID && nodeID != '') { if (nodeID == 'http://jabber.org/protocol/commands') { // XEP-0050: Ad-Hoc Commands q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items', node: nodeID}) .s('item', {jid: componentJID, node: 'ping', name: 'Ping'}) .s('item', {jid: componentJID, node: 'reload', name: 'Reload'}); response.cnode(q); return conn.send(response); } else { if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items', node: nodeID}); children = storage.getChildren(nodeID); if (typeof children == 'number') return makeError(response, children); } } else { q = xmpp.stanza('query', {xmlns: 'http://jabber.org/protocol/disco#items'}); children = storage.getChildren(); if (typeof children == 'number') return makeError(response, children); } for (var i in children) { var attr = {jid: componentJID}; if (children[i] == 'node') { if (config.enabled('meta-data')) { var metadata = storage.getMetadata(i); if (metadata['pubsub#title']) attr.name = metadata['pubsub#title']; } attr.node = i; // SECTION 5.5 } else attr.name = i; q.s('item', attr); } response.cnode(q); } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) { var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub'); // SECTION 5.6 if (pubsub.getChild('subscriptions')) { if (!config.enabled('retrieve-subscriptions')) return makeError(response, errors.subscriptions_retrieval_not_supported.n); var subscriptions = pubsub.getChild('subscriptions'); var subs; var nodeID = subscriptions.getAttribute('node'); if (nodeID && nodeID != '') { if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); subs = storage.getSubscription(toBareJID(to), node); } else subs = storage.getSubscription(toBareJID(to)); var s = xmpp.stanza('subscriptions'); for (i in subs) s.s('subscription', {node: i, jid: to, subscription: subs[i].type, subid: subs[i].subid}); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}); p.cnode(s); response.cnode(p); // SECTION 5.7 } else if (pubsub.getChild('affiliations')) { if (!config.enabled('retrieve-affiliations')) return makeError(response, errors.affiliations_retrieval_not_supported.n); var affiliations = pubsub.getChild('affiliations'); var nodeID = affiliations.getAttribute('node'); var affils; if (nodeID && nodeID != '') { if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); affils = {}; affils[nodeID] = storage.getAffiliation(toBareJID(to), nodeID); } else affils = storage.getAffiliationsFromJID(toBareJID(to)); var s = xmpp.stanza('affiliations'); for (i in affils) s.s('affiliation', {node: i, affiliation: affils[i]}); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}); p.cnode(s); response.cnode(p); // SECTION 6.3.2 } else if (pubsub.getChild('options')) { if (!config.enabled('subscription-options')) return makeError(response, errors.sub.configure.subscription_options_not_supported.n); var options = pubsub.getChild('options'); var nodeID = options.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var jid = options.getAttribute('jid'); if (!jid) return makeError(response, errors.sub.configure.subscriber_jid_required.n); if (toBareJID(jid) != toBareJID(to)) return makeError(response, errors.sub.configure.insufficient_privileges.n); var subs = storage.getSubscription(jid, nodeID); if (subs == {}) return makeError(response, errors.sub.configure.no_such_subscriber.n); var s = xmpp.stanza('options', {node: nodeID, jid: jid}); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}); var form = forms.build('form', 'subscribe_options', subs.options, true); s.cnode(form); p.cnode(s); response.cnode(p); // SECTION 6.4 } else if (pubsub.getChild('default')) { if (!config.enabled('retrieve-default-sub')) return makeError(response, errors.sub.default_options.default_subscription_configuration_retrieval_not_supported.n); var def = pubsub.getChild('default'); var nodeID = def.getAttribute('node'); if (nodeID && !storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}); var s; if (nodeID) s = xmpp.stanza('default', {node: nodeID}); else s = xmpp.stanza('default'); var form = forms.build('form', 'subscribe_options', 'default', false); s.cnode(form); p.cnode(s); response.cnode(p); // SECTION 6.5 } else if (pubsub.getChild('items')) { if (!config.enabled('retrieve-items')) return makeError(response, errors.sub.default_options.node_configuration_not_supported.n); var items = pubsub.getChild('items'); var nodeID = items.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var configuration = storage.getConfiguration(nodeID); if (configuration['pubsub#access_model'] == 'whitelist') { var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member') return makeError(response, errors.pub.publish.insufficient_privileges.n); } var item = []; for (var i=0; i<items.children.length; i++) { var j = items.children[i]; if (j.name == 'item' && j.attr['id'] && j.attr['id'] != '') item.push(j.attr['id']); } var max_items = items.getAttribute('max_items'); if (max_items) max_items = Number (max_items); if (item.length) { var s = xmpp.stanza('items', {node: nodeID}); for (var i=0; i<item.length; i++) { var j = storage.getItem(nodeID, item[i]); if (typeof j == 'number') return makeError(response, j); if (j == errors.success) continue; var k = xmpp.stanza('item', {id: item[i]}) k.cnode(j); s.cnode(k); } } else { var s = xmpp.stanza('items', {node: nodeID}); var j; if (max_items) j = storage.getLastItem(nodeID, max_items); else j = storage.getItems(nodeID); if (typeof j == 'number') return makeError(response, j); var k = 0; for (var i in j) { var contentItem = xmpp.stanza('item', {id: i}).t(j[i].content); s.cnode(contentItem); } } var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}); p.cnode(s); response.cnode(p); } else return makeError(response, errors.feature_not_implemented.n); } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) { var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner'); // SECTION 8.2 if (pubsub.getChild('configure')) { if (!config.enabled('config-node')) return makeError(response, errors.owner.configure.node_configuration_not_supported.n); var nodeID = pubsub.getChild('configure').getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only') return makeError(response, errors.pub.publish.insufficient_privileges.n); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'}); var s = xmpp.stanza('configure', {node: nodeID}); var form = forms.build('form', 'node_config', 'default', true); s.cnode(form); p.cnode(s); response.cnode(p); // SECTION 8.3 } else if (pubsub.getChild('default')) { if (!config.enabled('config-node')) return makeError(response, errors.owner.default_options.node_configuration_not_supported.n); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'}); var s = xmpp.stanza('default'); var form = forms.build('node_config', service_configuration.node_config, null, true); s.cnode(form); p.cnode(s); response.cnode(p); // SECTION 8.8 } else if (pubsub.getChild('subscriptions')) { if (!config.enabled('manage-subscriptions')) return makeError(response, errors.owner.manage_subscriptions.not_supported.n); var subscriptions = pubsub.getChild('subscriptions'); var nodeID = subscriptions.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner') return makeError(response, errors.forbidden.n); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'}); var s = xmpp.stanza('subscriptions', {node: nodeID}); var subs = storage.getSubscriptionsFromNodeID(nodeID) for (var jid in subs) s.s('subscription', {jid: jid, subscription: subs[jid].type, subid: subs[jid].subid}) p.cnode(s); response.cnode(p); // SECTION 8.9 } else if (pubsub.getChild('affiliations')) { if (!config.enabled('modify-affiliations')) return makeError(response, errors.owner.manage_affiliations.not_supported.n); var affiliations = pubsub.getChild('affiliations'); var nodeID = affiliations.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affils = storage.getAffiliationsFromNodeID(nodeID); var affil = affils[toBareJID(to)]; if (affil != 'super-owner' && affil != 'owner') return makeError(response, errors.owner.manage_affiliations.retrieve_list.entity_is_not_an_owner.n); var p = xmpp.stanza('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub#owner'}); var s = xmpp.stanza('affiliations', {node: nodeID}); for (var jid in affils) s.s('affiliation', {jid: jid, affiliation: affils[jid]}) p.cnode(s); response.cnode(p); } else return makeError(response, errors.feature_not_implemented.n); } else return makeError(response, errors.feature_not_implemented.n); } else if (type == 'set') { if (stanza.getChild('command', 'http://jabber.org/protocol/commands')) { // XEP-0050: Ad-Hoc Commands var command = stanza.getChild('command', 'http://jabber.org/protocol/commands'); var action = command.getAttribute('action'); if (action != 'execute') return makeError(response, errors.bad_request.n); var node = command.getAttribute('node'); if (node == 'ping') { var cmd = xmpp.stanza('command', {xmlns: 'http://jabber.org/protocol/commands', // sessionid: 'list:20020923T213616Z-700', node: node, 'status': 'completed'}) .c('note', {type: 'info'}).t('pong'); response.cnode(cmd); } else if (node == 'reload') { storage.load(); response.c('command', {xmlns: 'http://jabber.org/protocol/commands', node: node, 'status': 'completed'}) .c('note', {type: 'info'}).t('The server has correctly reloaded.'); } else return makeError(response, errors.bad_request.n); } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub')) { var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub'); // SECTION 6.1 if (pubsub.getChild('subscribe')) { if (!config.enabled('subscribe')) return makeError(response, errors.sub.subscribe.not_supported.n); var subscribe = pubsub.getChild('subscribe'); var nodeID = subscribe.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var configuration = storage.getConfiguration(nodeID); if (!configuration['pubsub#subscribe']) return makeError(response, errors.sub.subscribe.not_supported.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil == 'publish-only' || affil == 'outcast') return makeError(response, errors.pub.publish.insufficient_privileges.n); var jid = subscribe.getAttribute('jid'); if (!jid || toBareJID(jid) != toBareJID(to)) return makeError(response, errors.sub.subscribe.jids_do_not_match.n); // SECTION 6.3.7 var options = pubsub.getChild('options'); if (options && config.enabled('subscription-options')) { if (options.getAttribute('node') || options.getAttribute('jid')) return makeError(response, errors.bad_request.n); var x = options.getChild('x', 'jabber:x:data'); if (!x || x.getAttribute('type') != 'submit') return makeError(response, errors.bad_request.n); var form = forms.parse(x, true); if (typeof form == 'number') return makeError(response, form); var conf = form; } var subID; if (configuration['pubsub#access_model'] == 'open') { subID = storage.subscribe(nodeID, jid, 'subscribe', conf); if (typeof subID == 'number') return makeError(response, subID); } else if (configuration['pubsub#access_model'] == 'authorize') { subID = storage.subscribe(nodeID, jid, 'pending', conf); if (typeof subID == 'number') return makeError(response, subID); } else if (configuration['pubsub#access_model'] == 'whitelist') { var affil = storage.getAffiliation(jid, nodeID); if (affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'member') return makeError(response, errors.sub.subscribe.not_on_whitelist.n); subID = storage.subscribe(nodeID, jid, conf); if (typeof subID == 'number') return makeError(response, subID); } response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) .c('subscription', {node: nodeID, jid: jid, subid: subID.subid, subscription: subID.type}); if (conf) response.cnode(options); if (config.enabled('get-pending')) { var affiliates = storage.getAffiliationsFromNodeID(nodeID); var form = forms.build('form', 'subscribe_authorization', {allow: false, node: nodeID, subscriber_jid: jid}, true); //168 for (var i in affiliates) { if (affiliates[i] == 'super-owner' || affiliates[i] == 'owner') { var message = xmpp.message({to: i}).cnode(form); conn.send(message); } } } if (config.enabled('last-published')) { var last = storage.getLastItem(nodeID); if (typeof last != 'number') { var item = storage.getItem(nodeID, last); if (typeof item != 'number') { var attr = {}; attr[last] = {content: item}; sendNotifs(jid, 'items', nodeID, attr); } } } // SECTION 6.2 } else if (pubsub.getChild('unsubscribe')) { if (!config.enabled('subscribe')) return makeError(response, errors.sub.subscribe.not_supported.n); var unsubscribe = pubsub.getChild('unsubscribe'); var nodeID = unsubscribe.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var jid = unsubscribe.getAttribute('jid'); if (!jid || toBareJID(jid) != toBareJID(to)) return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n); var subID = storage.subscribe(nodeID, jid, 'none'); if (typeof subID == 'number') return makeError(response, subID); // SECTIONS 6.3.5 } else if (pubsub.getChild('options')) { if (!config.enabled('subscription-options')) return makeError(response, errors.sub.subscribe.not_supported.n); var options = pubsub.getChild('options'); var nodeID = unsubscribe.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var jid = unsubscribe.getAttribute('jid'); if (!jid || toBareJID(jid) != toBareJID(to)) return makeError(response, errors.sub.unsubscribe.insufficient_privileges.n); var x = options.getChild('x', 'jabber:x:data'); if (!x || x.getAttribute(type) != 'submit') return makeError(response, errors.bad_request); var form = forms.parse(x, true); if (typeof form == 'number') return makeError(response, form); var set = storage.configureSubscription(nodeID, jid, form); if (typeof form == 'number') return makeError(response, form); // SECTION 7.1 } else if (pubsub.getChild('publish')) { if (!config.enabled('publish')) return makeError(response, errors.pub.publish.item_publication_not_supported.n); var publish = pubsub.getChild('publish'); var nodeID = publish.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); var autocreate = false; if (!storage.existsNode(nodeID)) { if (config.enabled('auto-create')) autocreate = true; else return makeError(response, errors.node_does_not_exist.n); } var affil = storage.getAffiliation(toBareJID(to), nodeID); if (typeof affil == 'number' && affil != errors.node_does_not_exist.n) return makeError(response, affil); if (!autocreate && affil != 'super-owner' && affil != 'owner' && affil != 'publisher' && affil != 'publish-only') return makeError(response, errors.forbidden.n); var item = publish.getChild('item'); var itemID = item.getAttribute('id'); if (!config.enabled('item-ids') && itemID) return makeError(response, errors.itemid_required.n); itemID = itemID? itemID: utils.makeRandomId(); if (item.tags.length != 1) return makeError(response, errors.pub.publish.bad_payload.n); var conf = storage.getConfiguration(nodeID); var publishOptions = pubsub.getChild('publish-options'); if (publishOptions && config.enabled('publish-options')) { var x = publishOptions.getChild('x', 'jabber:x:data'); if (!x || x.getAttribute('type') != 'submit') return makeError(response, errors.bad_request.n); var form = forms.parse(x, true); if (form.access_model != conf['pubsub#access_model'] && !autocreate) return makeError(response, errors.pub.configuration.precondition.n); } if (!config.enabled('persistent-items')) { var notifs = storage.purgeNode(nodeID); if (typeof notifs == 'number') return makeError(response, r); } if (autocreate) { if (!form) form = {}; form['pubsub#creator'] = toBareJID(to); var r = storage.createNode(nodeID, form); if (typeof r == 'number') return makeError(response, r); } var content = item.getChild(); subscribers = storage.setItem(nodeID, itemID, content); if (typeof subscribers == 'number') return makeError(response, subscribers); var attrs = {}; if (content) attrs[itemID] = {content: content}; else attrs[itemID] = {}; sendNotifs(subscribers, 'items', nodeID, attrs); response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) .c('publish', {node: nodeID}) .c('item', {id: itemID}); // SECTION 7.2 } else if (pubsub.getChild('retract')) { if (!config.enabled('retract-items')) return makeError(response, errors.pub.retract.item_deletion_not_supported.n); var retract = pubsub.getChild('retract'); var nodeID = retract.getAttribute('node'); if (!nodeID || nodeID == '') return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var item = retract.getChild('item'); if (!item) return makeError(response, errors.pub.retract.item_or_itemid_required.n); var itemID = item.getAttribute('id') if (!itemID || itemID == '') return makeError(response, errors.pub.retract.item_or_itemid_required.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only') return makeError(response, errors.forbidden.n); var subscribers = storage.deleteItem(nodeID, itemID); if (typeof subscribers == 'number') return makeError(response, subscribers); var attrs = {}; attrs[itemID] = {}; sendNotifs(subscribers, 'items', nodeID, attrs, 'retract') // SECTION 8.1 } else if (pubsub.getChild('create')) { if (!config.enabled('create-nodes')) return makeError(response, errors.owner.create.node_creation_not_supported.n); var instant = false; var nodeID = pubsub.getChild('create').getAttribute('node'); if (!nodeID || nodeID == '') { if (!config.enabled('instant-nodes')) return makeError(response, errors.owner.create.instant_nodes_not_supported.n); nodeID = utils.makeRandomId(); instant = true; } if (storage.existsNode(nodeID)) return makeError(response, errors.nodeid_already_exists.n); var bare = toBareJID(to); var right = false; // Check for super-owner for (var i in config.owner) if (config.owner[i] == bare) right = true; // Check for authorized user for (var i in config.allowCreateNode) if (config.allowCreateNode[i].exec(bare)) right = true; if (!right) return makeError(response, errors.forbidden.n); var configure = pubsub.getChild('configure'); if (configure && config.enabled('create-and-configure')) { if (!config.enabled('config-node')) return makeError(response, errors.owner.configure.node_configuration_not_supported.n); if (configure.getAttribute('node')) return makeError(response, errors.bad_request.n); var x = configure.getChild('x', 'jabber:x:data'); if (!x || x.getAttribute('type') != 'submit') return makeError(response, errors.bad_request.n); var form = forms.parse(x, true); if (typeof form == 'number') return makeError(response, form); var conf = form; } if (!conf) conf = {}; conf['pubsub#creator'] = toBareJID(to); var r = storage.createNode(nodeID, conf); if (typeof r == 'number') return makeError(response, r); if (instant) response.c('pubsub', {xmlns: 'http://jabber.org/protocol/pubsub'}) .c('create', {node: nodeID}); } else return makeError(response, errors.feature_not_implemented.n); } else if (stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner')) { var pubsub = stanza.getChild('pubsub', 'http://jabber.org/protocol/pubsub#owner'); // SECTION 8.2.4 if (pubsub.getChild('configure')) { if (!config.enabled('config-node')) return makeError(response, errors.owner.configure.node_configuration_not_supported.n); var nodeID = configure.getAttribute('node'); if (!nodeID) return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner' && affil != 'publish-only') return makeError(response, errors.forbidden.n); var x = configure.getChild('x', 'jabber:x:data'); if (!x || x.getAttribute(type) != 'submit') return makeError(response, errors.bad_request.n); var form = forms.parse(x, true); if (typeof form == 'number') return makeError(response, form); var conf = form; var set = storage.configure(nodeID, conf); if (typeof set == 'number') return makeError(response, set); // SECTION 8.4 } else if (pubsub.getChild('delete')) { if (!config.enabled('delete-nodes')) return makeError(response, errors.feature_not_implemented.n); //XXX var del = pubsub.getChild('delete'); var nodeID = del.getAttribute('node'); if (!nodeID) return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner') return makeError(response, errors.forbidden.n); var notifs = storage.deleteNode(nodeID); if (typeof notifs == 'number') return makeError(response, r); sendNotifs(notifs, 'delete', nodeID); // SECTION 8.5 } else if (pubsub.getChild('purge')) { if (!config.enabled('purge-nodes')) return makeError(response, errors.owner.purge.node_purging_not_supported.n); //XXX var purge = pubsub.getChild('purge'); var nodeID = purge.getAttribute('node'); if (!nodeID) return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner') return makeError(response, errors.forbidden.n); if (!config.enabled('persistent-items')) //FIXME: autre condition, supporté par le node return makeError(response, errors.owner.purge.node_does_not_persist_items.n); var notifs = storage.purgeNode(nodeID); if (typeof notifs == 'number') return makeError(response, r); sendNotifs(notifs, 'purge', nodeID); // SECTION 8.8.2 } else if (pubsub.getChild('subscriptions')) { if (!config.enabled('manage-subscriptions')) return makeError(response, errors.owner.manage_subscriptions.not_supported.n); //XXX var subscriptions = pubsub.getChild('subscriptions'); var nodeID = subscriptions.getAttribute('node'); if (!nodeID) return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner') return makeError(response, errors.forbidden.n); var e = false; for (i in subscriptions.tags) { var jid = subscriptions.tags[i].getAttribute('jid'); var subscription = subscriptions.tags[i].getAttribute('subscription'); var set = storage.subscribe(nodeID, jid, subscription); if (typeof set == 'number') e = true; else { // SECTION 8.8.4 sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: subscription}); subscriptions.tags.splice(i, 1); } } if (e) return makeError(response, errors.owner.manage_subscriptions.modify.multiple_simultaneous_modifications.n, pubsub); // SECTION 8.9.2 } else if (pubsub.getChild('affiliations')) { if (!config.enabled('modify-affiliations')) return makeError(response, errors.owner.manage_affiliations.not_supported.n); //XXX var affiliations = pubsub.getChild('affiliations'); var nodeID = affiliations.getAttribute('node'); if (!nodeID) return makeError(response, errors.nodeid_required.n); if (!storage.existsNode(nodeID)) return makeError(response, errors.node_does_not_exist.n); var affil = storage.getAffiliation(toBareJID(to), nodeID); if (affil != 'super-owner' && affil != 'owner') return makeError(response, errors.forbidden.n); var e = false; for (i in affiliations.children) { var jid = affiliations.children[i].getAttribute('jid'); var affiliation = affiliations.children[i].getAttribute('affiliation'); var set = storage.setAffiliation(nodeID, jid, affiliation); if (typeof set == 'number') e = true; else { // SECTION 8.9.4 sendNotifs(jid, 'affiliations', nodeID, {jid: jid, affiliation: affiliation}); affiliations.children.splice(i, 1); } } if (e) return makeError(response, errors.owner.manage_affiliations.modify.multiple_simultaneous_modifications.n, pubsub); } else return makeError(response, errors.feature_not_implemented.n); } else return makeError(response, errors.feature_not_implemented.n); } else return makeError(response, errors.feature_not_implemented.n); conn.send(response); } function onMessage(stanza) { var from = stanza.getAttribute('to'); var to = stanza.getAttribute('from'); var id = stanza.getAttribute('id'); var response; if (id) response = xmpp.message({to: to, from: from, id: id}); else response = xmpp.message({to: to, from: from}); var x = stanza.getChild('x', 'jabber:x:data'); if (x) { var form = forms.parse(x); if (form.type == 'submit' && form.fields.FORM_TYPE.value == 'subscribe_authorization') { if (form.fields.subid && form.fields.subid.value) var subID = form.fields.subid.value; if (form.fields.node && form.fields.node.value) var nodeID = form.fields.node.value; if (form.fields.subscriber_jid && form.fields.subscriber_jid.value) var jid = form.fields.subscriber_jid.value; if (form.fields.allow && form.fields.allow.value) var allow = form.fields.allow.value; var type = allow? 'subscribed': 'none'; var set = storage.subscribe(nodeID, jid, type) //if (set.subid != subID) //TODO: support the multi-subscribe feature sendNotifs(jid, 'subscription', nodeID, {jid: jid, subscription: type}); } else return makeError(response, errors.feature_not_implemented.n); } else return makeError(response, errors.feature_not_implemented.n); conn.send(response) } function onPresence(stanza) { var from = stanza.getAttribute('to'); var to = stanza.getAttribute('from'); var id = stanza.getAttribute('id'); var response; if (id) response = xmpp.presence({to: to, from: from, id: id}); else response = xmpp.presence({to: to, from: from}); makeError(response, errors.feature_not_implemented.n); } function makeError(response, errorNumber, payload) { response.attr.type = 'error'; if (payload) response.cnode(payload); var e = errors.reverse[errorNumber]; var error = xmpp.stanza('error', {type: e.type}); error.s(e.error, {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}) if (e.reason) { if (e.feature) error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors', feature: e.feature}); else error.s(e.reason, {xmlns: 'http://jabber.org/protocol/pubsub#errors'}); } response.cnode(error); conn.send(response); } function sendNotifs(notifs, type, nodeID, a1, a2) { var ev = xmpp.stanza('event', {xmlns: 'http://jabber.org/protocol/pubsub#event'}); if (type == 'affiliations') { ev.attr.xmlns = 'http://jabber.org/protocol/pubsub'; var args = {}; for (i in a1) { var attr = a1[i]; if (i == 'affiliation') args.affiliation = attr; else if (i == 'jid') args.jid = attr; } var affiliations = xmpp.stanza('affiliations', {node: nodeID}) .c('affiliation', args); ev.cnode(affiliations); } else if (type == 'collection') { var collection = xmpp.stanza('collection', {node: nodeID}); if (a1 == 'associate') collection.cnode('associate', {node: nodeID}); else collection.cnode('disassociate', {node: nodeID}); ev.cnode(collection); } else if (type == 'configuration') { if (!config.enabled('config-node')) { _('Error #4', 41) return; } var configuration = xmpp.stanza('configuration', {node: nodeID}); if (a1) { var x = forms.build('node_config', service_configuration.node_config, storage.getConfiguration(nodeID)); if (x) configuration.cnode(x); //TODO: voir exemple 150 } ev.cnode(configuration); } else if (type == 'delete') { var del = xmpp.stanza('delete', {node: nodeID}); if (a1) del.c('redirect', {uri: a1}); ev.cnode(del); } else if (type == 'items') { var items = xmpp.stanza(type, {node: nodeID}); if (a2 == 'retract') for (var i in a1) items.s('retract', {id: i}); else { for (var i in a1) { var item = a1[i]; var args = {}; if (i != '') args.id = i; if (item.node) args.node = item.node; if (item.publisher) args.publisher = item.publisher; var it = xmpp.stanza('item', args); if (item.content) it.cnode(item.content); items.cnode(it); } } ev.cnode(items); } else if (type == 'purge') { ev.c('purge', {node: nodeID}); } else if (type == 'subscription') { if (!config.enabled('subscription-notifications')) return; var args = {node: nodeID}; for (i in a1) { var attr = a1[i]; if (i == 'subscription') { if (attr == 'none' || attr == 'pending' || attr == 'subscribed' || attr == 'unconfigured') args[i] = attr; else { _('Error #3', 41) return; } } else if (i == 'jid' || i == 'subid') args[i] = attr; else if (i == 'expiry') args[i] = attr.toString(); } if (!args.jid || args.jid == '') { _('Error #2', 41) return; } var sub = xmpp.stanza('subscription', args); ev.cnode(sub); } else { _('Error #1', 41) return; } var subs; if (typeof notifs == 'string') { subs = {}; subs[notifs] = storage.getSubscription(notifs, nodeID); } else subs = notifs; for (var i in subs) { var sub = subs[i]; if (sub.options) { if (typeof sub.options['pubsub#deliver'] != 'undefined' && !sub.options['pubsub#deliver']) continue; if (typeof sub.options['pubsub#digest'] != 'undefined' && sub.options['pubsub#digest']) { if (!sub.digest) sub.digest = []; sub.digest.push(ev) if (sub.digestTimeout) continue; var freq; if (typeof sub.options['pubsub#digest_frequency'] == 'undefined') freq = 0; else freq = parseInt(sub.options['pubsub#digest_frequency']); if (freq == 0) freq = 24*60*60*1000; setTimeout(sendDigest, freq, notifs[i], nodeID); sub.digestTimeout = true; continue; } } var message = xmpp.message({to: i, from: componentJID, id: conn.getUniqueId()}); message.cnode(ev); conn.send(message); } } function sendDigest(jid, nodeID) { var sub = storage.getSubscription(jid, nodeID); if (sub.digestTimeout) sub.digestTimeout = false; var message = xmpp.message({to: jid, from: componentJID, id: conn.getUniqueId()}); for (var i in sub.digest) message.cnode(sub.digest[i]); conn.send(message); } conn.connect(componentJID, componentPassword, function (status, condition) { if (status == xmpp.Status.CONNECTED) { conn.addHandler(onMessage, null, 'message', null, null, null); conn.addHandler(onIq, null, 'iq', null, null, null); conn.addHandler(onPresence, null, 'presence', null, null, null); if (process.argv.length >= 3) storage.load(process.argv[2]); else storage.load(); var stdin = process.openStdin(); stdin.setEncoding('utf8'); stdin.addListener('data', storage.debug); } else conn.log(xmpp.LogLevel.DEBUG, 'New connection status: ' + status + (condition? (' ('+condition+')'): '')); });