diff --git a/examples/c2s.js b/examples/c2s.js index f648d33fb..61103e772 100644 --- a/examples/c2s.js +++ b/examples/c2s.js @@ -1,119 +1,39 @@ var xmpp = require('../lib/node-xmpp'); -var SoftwareVersion = require('./c2s_mods/software_version'); -/* - * TODO - * - support for Presence module - * - Support for TLS () : https://groups.google.com/group/nodejs/browse_thread/thread/1e8e6501493d63b8# - * - Admin tools - * - Component interface - * - Plugins for 'well-known' services (Roster, PubSub, PEP) - * - Logging - * - Shapers - * - In-band registration - * - mods : - * - presence - * - offline - * - announce - * - caps - * - muc - * - roster - * - PEP - */ +/* This is a very basic C2S example. One of the key design decisions of node-xmpp is to keep it very lightweight */ +/* If you need a full blown server check out https://github.com/superfeedr/xmpp-server */ // Sets up the server. var c2s = new xmpp.C2S({ port: 5222, - domain: 'localhost', - tls: { - keyPath: './examples/localhost-key.pem', - certPath: './examples/localhost-cert.pem' - } + domain: 'localhost'//, + // tls: { + // keyPath: './examples/localhost-key.pem', + // certPath: './examples/localhost-cert.pem' + // } + }); // Allows the developer to authenticate users against anything they want. -c2s.on("authenticate", function(jid, password, client) { - if(password == "password") { - client.emit("auth-success", jid); - } - else { - client.emit("auth-fail", jid); - } +c2s.on("authenticate", function(jid, password, client, cb) { + cb(true); // cb(false); +}); + +// Allows the developer to register the jid against anything they want +c2s.on("register", function(jid, password, client, cb) { + cb(true); // cb(false, {code: "406", type: "modify", text: "not-acceptable"}); }); // On Connect event. When a client connects. c2s.on("connect", function(client) { // That's the way you add mods to a given server. - SoftwareVersion.name = "Node XMPP server example"; - SoftwareVersion.version = "0.0.0.1"; - SoftwareVersion.os = "Mac OS X 10.7 Lion"; - client.addMixin(SoftwareVersion.mod); }); // On Disconnect event. When a client disconnects c2s.on("disconnect", function(client) { - }); // Most imoortant pieces of code : that is where you can configure your XMPP server to support only what you care about/need. c2s.on("stanza", function(stanza, client) { - var query, vCard; - // We should provide a bunch of "plugins" for the functionalities below. - - // No roster support in this server! - if (stanza.is('iq') && (session = stanza.getChild('query', 'jabber:iq:roster'))) { - stanza.attrs.type = "error"; - stanza.attrs.to = stanza.attrs.from; - delete stanza.attrs.from; - client.send(stanza); - } - // No private support on this server - else if (stanza.is('iq') && (query = stanza.getChild('query', "jabber:iq:private"))) { - stanza.attrs.type = "error"; - stanza.attrs.to = stanza.attrs.from; - delete stanza.attrs.from; - client.send(stanza); - } - // No vCard support on this server. - else if (stanza.is('iq') && (vCard = stanza.getChild('vCard', "vcard-temp"))) { - stanza.attrs.type = "error"; - stanza.attrs.to = stanza.attrs.from; - delete stanza.attrs.from; - client.send(stanza); - } - // No DiscoInfo on this server. - else if (stanza.is('iq') && (query = stanza.getChild('query', "http://jabber.org/protocol/disco#info"))) { - stanza.attrs.type = "error"; - stanza.attrs.to = stanza.attrs.from; - delete stanza.attrs.from; - client.send(stanza); - } - // No Version support on this server. - else { - - } -}) - - -// You can also decide to rewrite many things, like for example the way you route stanzas. -// This will allow for clustering for your node-xmpp server, using redis's PubSub feature. -// To run this example in its full "power", just run node exmaple c2s.js from 2 different machines, as long as they share the redis server, they should be able to communicate! -// var sys = require("sys"); -// var redis = require("redis-node"); -// var redispub = redis.createClient(); -// var redissub = redis.createClient(); -// -// xmpp.C2S.prototype.route = function(stanza) { -// var self = this; -// if(stanza.attrs && stanza.attrs.to) { -// var toJid = new xmpp.JID(stanza.attrs.to); -// redispub.publish(toJid.bare().toString(), stanza.toString()); -// } -// } -// xmpp.C2S.prototype.registerRoute = function(jid, client) { -// redissub.subscribeTo(jid.bare().toString(), function(channel, stanza, pattern) { -// client.send(stanza); -// }); -// return true; -// } +}); \ No newline at end of file diff --git a/examples/c2s_mods/software_version.js b/examples/c2s_mods/software_version.js deleted file mode 100644 index f22d4de1c..000000000 --- a/examples/c2s_mods/software_version.js +++ /dev/null @@ -1,46 +0,0 @@ -var xmpp = require('node-xmpp'); - -// // XEP-0092: Software Version -exports.name = "mod_version"; - -exports.default = { - name: "node-xmpp Server", - version: "1.3.3.7", - os: "earth OS" -}; - -function SoftwareVersionMixin(client) { - client.on('stanza', function(stanza) { - if (stanza.is('iq') && (query = stanza.getChild('query', "jabber:iq:version"))) { - stanza.attrs.type = "result"; - stanza.attrs.to = stanza.attrs.from; - delete stanza.attrs.from; - - // Actual version attributes - if(typeof(exports.name) === "undefined") { - query.c("name").t(exports.default.name); - } - else { - query.c("name").t(exports.name); - } - - if(typeof(exports.version) === "undefined") { - query.c("version").t(exports.default.version); - } - else { - query.c("version").t(exports.version); - } - - if(typeof(exports.os) === "undefined") { - query.c("os").t(exports.default.os); - } - else { - query.c("os").t(exports.os); - } - client.send(stanza); - } - }); -} - -exports.mod = SoftwareVersionMixin; - diff --git a/examples/localhost-cert.pem b/examples/localhost-cert.pem deleted file mode 100644 index ab96ebf18..000000000 --- a/examples/localhost-cert.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICATCCAWoCCQDnhGmqEMOAnTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB -VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 -cyBQdHkgTHRkMB4XDTExMDgyNjIwMDkxM1oXDTExMDkyNTIwMDkxM1owRTELMAkG -A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0 -IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA26k/ -+Nb9PgjAIMlnMCnZuTfuXsKi5AG8ZWlD/iBrXmS+bDnlclsVoUExVz/Z8ZCJfUcY -COHmVVwNiOtRBuSwfpQ9G0RLOKSmxRhd8v7pmV20poqcw7jtk8T4YlbB2a/TafgT -pxLKN12C8p+NODQTQSjg0XPnLdk+xwEna7/m4zcCAwEAATANBgkqhkiG9w0BAQUF -AAOBgQCE0tU6WZ0byq+kLPnR3aZlbh+Zu/8fWrnhqhx2I8zs15v8yLq34D3AplOl -kahYzqOhJnr52xJ8tLf6TeZxRVk6Nl2B94Y76abcD8p1TwWOlOBd8Omt6XnUxvp0 -QEFNdDLh8H4PSj5LxAF+GwceoTaB3typYMw64aM/jYJisNxcXg== ------END CERTIFICATE----- diff --git a/examples/localhost-csr.pem b/examples/localhost-csr.pem deleted file mode 100644 index 64f120929..000000000 --- a/examples/localhost-csr.pem +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh -MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDbqT/41v0+CMAgyWcwKdm5N+5ewqLkAbxlaUP+IGteZL5s -OeVyWxWhQTFXP9nxkIl9RxgI4eZVXA2I61EG5LB+lD0bREs4pKbFGF3y/umZXbSm -ipzDuO2TxPhiVsHZr9Np+BOnEso3XYLyn404NBNBKODRc+ct2T7HASdrv+bjNwID -AQABoAAwDQYJKoZIhvcNAQEFBQADgYEAMpd7dWOruYc5sNVQ1NX5qJd9LnYcq0wq -FjoNSjza+aT/ZC/D8vQXRwGpwnZJzFjo0EqF+jMsDMQ75GeDCqxMYsZ9oX9fcip/ -h0h5wUKfmzoKytDcUdNg0P3Jjag0rxNVYnZ7Z//bt/zOX4gfbmIOG1ChkA3UT1XB -RxaRrC0yPA8= ------END CERTIFICATE REQUEST----- diff --git a/examples/localhost-key.pem b/examples/localhost-key.pem deleted file mode 100644 index b28e31216..000000000 --- a/examples/localhost-key.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQDbqT/41v0+CMAgyWcwKdm5N+5ewqLkAbxlaUP+IGteZL5sOeVy -WxWhQTFXP9nxkIl9RxgI4eZVXA2I61EG5LB+lD0bREs4pKbFGF3y/umZXbSmipzD -uO2TxPhiVsHZr9Np+BOnEso3XYLyn404NBNBKODRc+ct2T7HASdrv+bjNwIDAQAB -AoGALCmC6wyOLYKk3fKgBFblpw9PpU2MYjrXHaxkNxtdesTWY/vf3XXii2EIAnqb -3q0odz9r3Z1NKawwLG7jA3fG48yfVfgOB89RKNpaeaFI+tgmtKEGSpV0nlXBfbz5 -ijjre0oax0fIyKuLudKVusXM6x2+sRcSELBUfTyt7Z7/ZUECQQDuekx13a5i1mlL -ty5+plkmIxzZHIrwemAoAyyRgNQ9B79gv11tVC9LPtgSKLUZvpInp9N9M/PYawjb -/sEsToZTAkEA680EIdF8oWMVySrPJyXy8b+b4mfnawyyZrb38AMske1QVK+AJ2cZ -3LYWizmp5I58v3BZZwSF9sda9JoWqbmLDQJAcSgUycSzb3Cfq+6yoCPcn0z/7kEt -6CeZIsNGzY/cpUzc0VJcSXktq72ZFxy7ugiEH07x+jHBncpD/Odnz9c+wwJBAK6s -vLbgnFZhNC5FTBdQAVwb+LoLBl9ClJWKTLi7YTToe22AZIrL0ViyepjAKp5o/vhB -nbi2nRZmL3aq/sbT77ECQQDVIDfGlfLzHSEg67wyiKY9MgdrxxCfCQWXVMpuK846 -Ruy0IgjINq+90Cc5rPFeTnrsMm0iJ/ps1+OH2+ybQxea ------END RSA PRIVATE KEY----- diff --git a/lib/xmpp/c2s.js b/lib/xmpp/c2s.js index f9e24ae99..203316c4e 100644 --- a/lib/xmpp/c2s.js +++ b/lib/xmpp/c2s.js @@ -25,7 +25,7 @@ function C2S(options) { // And now start listening to connections on the port provided as an option. net.createServer(function (inStream) { self.acceptConnection(inStream, options); - }).listen(options.port, options.host); + }).listen(options.port); // Load TLS key material if (options.tls) @@ -52,7 +52,7 @@ C2S.prototype.acceptConnection = function(socket, options) { this.emit("connect", client); socket.addListener('error', function() { }); client.addListener('error', function() { }); -} +}; /** * C2S Router */ @@ -72,7 +72,7 @@ C2S.prototype.route = function(stanza) { else { // Huh? Who is it for? and why did it end up here? } -} +}; /** * Registers a route (jid => specific client connection) @@ -83,7 +83,7 @@ C2S.prototype.registerRoute = function(jid, client) { this.sessions[jid.bare().toString()] = {}; this.sessions[jid.bare().toString()][jid.resource] = client; return true; -} +}; /** * Unregisters a route (jid => specific client connection) @@ -95,7 +95,7 @@ C2S.prototype.unregisterRoute = function(jid, client) { delete this.sessions[jid.bare().toString()][jid.resource]; } return true; -} +}; function C2SClient(socket, c2s) { @@ -117,18 +117,6 @@ function C2SClient(socket, c2s) { self.onRawStanza(stanza); }); - this.addListener('auth-success', function(jid) { - self.jid = jid; - self.authentified = true; - self.stopParser(); - self.send(new ltx.Element("success", { xmlns: NS_XMPP_SASL })); - self.startParser(); - }); - - this.addListener('auth-fail', function(jid) { - self.send(new ltx.Element("failure", { xmlns: NS_XMPP_SASL })); - }); - this.addListener('close', function() { // We need to remove this user's connection, if authed: if (self.jid) { @@ -140,6 +128,7 @@ function C2SClient(socket, c2s) { return self; }; sys.inherits(C2SClient, Connection.Connection); +exports.C2SClient = C2SClient; C2SClient.prototype.startStream = function(streamAttrs) { var attrs = {}; @@ -169,14 +158,14 @@ C2SClient.prototype.startStream = function(streamAttrs) { this.send(s.substr(0, s.indexOf(' '))); this.sendFeatures(); -} +}; C2SClient.prototype.sendFeatures = function() { var features = new ltx.Element('stream:features'); if(!this.authentified) { // TLS if(this.c2s.options.tls && !this.socket.encrypted) { - features.c("starttls", {xmlns: NS_XMPP_TLS}).c("required") + features.c("starttls", {xmlns: NS_XMPP_TLS}).c("required"); } this.mechanisms = sasl.availableMechanisms(); @@ -190,7 +179,7 @@ C2SClient.prototype.sendFeatures = function() { features.c("session", {xmlns: 'urn:ietf:params:xml:ns:xmpp-session'}); } this.send(features); -} +}; C2SClient.prototype.onRawStanza = function(stanza) { var bind, session; @@ -222,16 +211,33 @@ C2SClient.prototype.onRawStanza = function(stanza) { stanza.attrs.to != this.jid.bare().toString()) { this.c2s.router.send(stanza); } + else if(stanza.is('iq') && + (inband = stanza.getChild('query', 'jabber:iq:register'))) { + this.onRegistration(stanza); + } else { // Let's trigger self for Mixins this.emit('stanza', stanza, this); // Then send it to the user code, just in case the user wants to interract with it. this.c2s.emit('stanza', stanza, this); } -} +}; C2SClient.prototype.authenticate = function(username, password) { - this.c2s.emit('authenticate', new JID(username, this.c2s.options.domain), password, this); + var self = this; + var jid = new JID(username, this.c2s.options.domain); + this.c2s.emit('authenticate', jid, password, this, function(authenticated) { + if(authenticated) { + self.jid = jid; + self.authentified = true; + self.stopParser(); + self.send(new ltx.Element("success", { xmlns: NS_XMPP_SASL })); + self.startParser(); + } + else { + self.send(new ltx.Element("failure", { xmlns: NS_XMPP_SA0SL })); + } + }); }; C2SClient.prototype.onAuth = function(stanza) { @@ -247,9 +253,32 @@ C2SClient.prototype.onAuth = function(stanza) { } }; +C2SClient.prototype.onRegistration = function(stanza) { + var self = this; + register = stanza.getChild('query', 'jabber:iq:register'); + if(stanza.attrs.type === 'get') { + stanza.attrs.type = "result"; + register.c("instructions").t("Choose a username and password for use with this service. "); + register.c("username"); + register.c("password"); + self.send(stanza); + } + else if(stanza.attrs.type === 'set') { + self.c2s.emit('register', new JID(register.getChild('username', 'jabber:iq:register').getText(), this.c2s.options.domain), register.getChild('password', 'jabber:iq:register').getText(), self, function(registered, reason) { + if(registered) { + self.send(new ltx.Element("iq", {type: "result", id: stanza.attrs.id})); + } else { + stanza.attrs.type = "error"; + stanza.c("error", {code: reason.code, type: reason.type}).c(reason.text, {xmlns: "urn:ietf:params:xml:ns:xmpp-stanzas"}); + self.send(stanza); + } + }); + } +}; + C2SClient.prototype.onBind = function(stanza) { var self = this; - var bind = stanza.getChild('bind', 'urn:ietf:params:xml:ns:xmpp-bind') + var bind = stanza.getChild('bind', 'urn:ietf:params:xml:ns:xmpp-bind'); if(resource = bind.getChild("resource", 'urn:ietf:params:xml:ns:xmpp-bind')) { self.jid.setResource(resource.getText()); } @@ -259,12 +288,12 @@ C2SClient.prototype.onBind = function(stanza) { if(self.c2s.registerRoute(self.jid, self)) { self.send(new ltx.Element("iq", {type:"result", id: stanza.attrs.id}).c("bind", { xmlns: "urn:ietf:params:xml:ns:xmpp-bind"}).c("jid").t(self.jid.toString())); } -} +}; C2SClient.prototype.onSession = function(stanza) { var self = this; self.send(new ltx.Element("iq", {type:"result", id: stanza.attrs.id}).c("session", { xmlns: "urn:ietf:params:xml:ns:xmpp-session"})); -} +}; function generateId() { diff --git a/lib/xmpp/sasl.js b/lib/xmpp/sasl.js index ce05789fa..3019b52ee 100644 --- a/lib/xmpp/sasl.js +++ b/lib/xmpp/sasl.js @@ -41,7 +41,7 @@ function Plain() { var params = auth.split("\x00"); this.username = params[1]; client.authenticate(this.username, params[2]); - } + }; } sys.inherits(Plain, Mechanism); @@ -175,7 +175,7 @@ function DigestMD5() { return false; } this.response = dict.response; - return true + return true; }; diff --git a/package.json b/package.json index d907b1558..8a3e94417 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,13 @@ ,"author": "Stephan Maka" ,"dependencies": {"node-expat": ">=1.3.1" ,"ltx": ">= 0.0.4" + ,"node-stringprep" : ">=0.0.5" } + ,"devDependencies" : { "v8-profiler" : ">=0.0.3" + ,"vows": ">= 0.5.9" + ,"nodemock": ">= 0.2.12" + ,"horaa": ">= 0.1.1alpha" + } ,"repositories": [{"type": "git" ,"path": "git://github.com/astro/node-xmpp.git" }] diff --git a/test/test_c2s.js b/test/test_c2s.js new file mode 100644 index 000000000..fe2f94395 --- /dev/null +++ b/test/test_c2s.js @@ -0,0 +1,577 @@ + +var sys = require("sys"); +var horaa = require('horaa'); +var ltx = require('ltx'); + +var nodemock = require("nodemock"); +var xmpp = require("xmpp"); + +var vows = require('vows'), +assert = require('assert'); + +var C2S = require('xmpp/c2s').C2S; + +var NS_JABBER_IQ_ROSTER = 'jabber:iq:roster'; +var NS_XMPP_SASL = "urn:ietf:params:xml:ns:xmpp-sasl"; +var NS_XMPP_BIND = "urn:ietf:params:xml:ns:xmpp-bind"; +var NS_XMPP_SESSION = "urn:ietf:params:xml:ns:xmpp-session"; +var NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams"; +var NS_XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"; + +function sizeOf(obj) { + + var size = 0, key; + for (key in obj) { + if (obj.hasOwnProperty(key)) size++; + } + return size; +} + +function xmlToObject( featuresStr ) { + + sys.puts( "server sent:"); + sys.puts( featuresStr ); + sys.puts( "\n\n"); + + try { + return ltx.parse(featuresStr); + } catch (e) { + assert.isTrue( false ); + } +} + +function streamFromClient(c2sClient, clientStream) { + + sys.puts( "server retrieved:"); + sys.puts( clientStream); + sys.puts( "\n\n"); + + c2sClient.onData(clientStream); +} + +function c2sClientInitiatesStream(domain, client) +{ + var clientStream = ""; + clientStream += ""; + clientStream += ""; + streamFromClient(client, clientStream); +} + +function clientSendAuth(client, mechanismType, encodedResponse ) { + var clientStream = ""; + clientStream += "" + encodedResponse +""; + streamFromClient(client, clientStream); +} + +function clientSendResponse(client, encodedResponse ) { + var clientStream = ""; + clientStream += ""; + streamFromClient(client, clientStream); +} + +function clientSendAbort(client, encodedResponse ) { + var clientStream = ""; + clientStream += ""; + streamFromClient(client, clientStream); +} + +function verifyChallengeTag(socket, challenge) +{ + var challengeTag = xmlToObject( socket.streamResult ); + socket.streamResult = ''; + assert.equal (challengeTag.is('challenge', NS_XMPP_SASL), true ); + assert.equal (challengeTag.getText(), challenge ); +} + +function verifySuccessTag(socket ) { + var success = xmlToObject( socket.streamResult ); + socket.streamResult = ''; + assert.equal ( success.is('success', NS_XMPP_SASL), true); +} + +function verifyAuthError(socket, errortype) +{ + var failureTag = getError( socket ); + assert.equal (failureTag.is('failure', NS_XMPP_SASL), true ); + var message = failureTag.getChild(errortype, NS_XMPP_SASL); + assert.equal ('<'+errortype+'/>',message.toString() ); +} + +function xmppAuthSucceed( domain, client, socket ) { + // 6.5.9: Server informs client of successful authentication: + verifySuccessTag(socket); + + /* + // 6.5.10: Client initiates a new stream to server + c2sClientInitiatesStream( domain, client ); + // 6.5.11: Server responds by sending a stream header to client : + var features = readFeatures( socket ); + assert.equal ( true, features.is('features') ); + var bind = features.getChild('bind', NS_XMPP_BIND); + assert.isNotNull ( bind ); + var session = features.getChild('bind', NS_XMPP_SESSION); + assert.isNotNull ( session );*/ +} + + +function clientSendIqBind(client, resourceId, iqtype) { + + if ( !iqtype ) { + iqtype = 'set'; + } + var clientStream = ""; + clientStream += ""; + clientStream += ""; + clientStream += ""+resourceId+""; + clientStream += ""; + clientStream += ""; + streamFromClient(client, clientStream); +} + +function clientSendEmptyIqBind(client) { + + var clientStream = ""; + clientStream += ""; + clientStream += ""; + clientStream += ""; + streamFromClient(client, clientStream); +} + +function verifyIqBindResult(socket, userName, domain, resourceId, currentJid){ + var iqTag = xmlToObject( socket.streamResult ); + socket.streamResult = ''; + assert.equal ( iqTag.is('iq'), true); + assert.equal ( iqTag.attrs['type'], 'result'); + var bindTag = iqTag.getChild('bind', NS_XMPP_BIND); + var jidTag = bindTag.getChild('jid', NS_XMPP_BIND); + var jid = new xmpp.JID( jidTag.getText() ); + assert.equal ( jid.user, userName); + assert.equal ( jid.domain, domain); + assert.equal ( jid.resource, resourceId ); + assert.equal ( currentJid.user, userName); + assert.equal ( currentJid.domain, domain); + assert.equal ( currentJid.resource, resourceId ); +} + +var api = { + createC2S: function(domain){ + return function() { + var port = 5222; + var router_port = 5269; + + var mockedServerSocket = nodemock.mock("listen").takes( port ); + var mockedRouterServerSocket = nodemock.mock("listen").takes( router_port, "::" ); + + var counter = 0; + var response = null; + var router_response = null; + var httpsHoraa = horaa('net'); + httpsHoraa.hijack('createServer', function( cb ) { + counter++; + if ( counter == 1 ) { + response = cb; + return mockedServerSocket; + } else { + router_response = cb; + return mockedRouterServerSocket; + } + } ); + + + var server = new C2S( { 'domain' : domain + , "port" : port + } ); + + return { 'server' : server, 'domain' : domain, 'acceptctrl' : response }; + }; + }, + createC2SClient: function(domain){ + return function(params) { + + var server = params.server; + + server.addListener( "connect", function( c2sclient ) { + params.client = c2sclient; + }); + // arrange + //sys.puts( server.clients ); + + //sys.puts("createIncomingClient\n\n"); + var mockSocket = {}; + mockSocket.addListener = function( e, cb ){}; + mockSocket.writable = true; + mockSocket.streamResult = ''; + mockSocket.setKeepAlive = function( a, b ){}; + mockSocket.write = function(data) { this.streamResult += data.toString();}; + mockSocket.end = function() {}; + assert.equal( sizeOf(server.sessions), 0 ); + // act + params.acceptctrl(mockSocket); + // assert + assert.equal( sizeOf(server.sessions), 0 ); + + params['socket'] = mockSocket; + return params; + }; + }, + c2sServerStartStream : function(){ + return function( params ){ + //sys.puts("serverStartStream\n\n"); + + // 6.5.1 Client initiates stream to server: + var client = params.client; + var socket = params.socket; + c2sClientInitiatesStream( params.server.options.domain, client ); + // 6.5.2 Server responds with a stream tag sent to client: + // and 6.5.3 Server informs client of available authentication mechanisms + var features = readFeatures( socket ); + assert.equal ( true, features.is('features') ); + var mechanisms = features.getChild('mechanisms', NS_XMPP_SASL); + assert.isNotNull ( mechanisms); + var mechanismChildren = mechanisms.getChildren('mechanism', NS_XMPP_SASL); + assert.equal ("PLAIN", mechanismChildren[0].getText() ); + return params; + }; + }, + fastSASLNegotiation: function (userName) { + return function( params ) { + + var client = params.client; + var socket = params.socket; + var domain = params.server.options.domain; + params.server.addListener('authenticate', function ( jid, password, client, cb ) { + cb(true); + } ); + + clientSendAuth(client, 'PLAIN', getPlainAuthByUserName(userName)); + + + // 6.5.9 - 6.5.11 + xmppAuthSucceed( domain, client, socket ); + params.userName = userName; + + return params; + }; + }, + slowSASLNegotiation: function (userName) { + return function( params ) { + var client = params.client; + var socket = params.socket; + var domain = params.server.options.domain; + + params.server.addListener('authenticate', function ( jid, password, client, cb ) { + cb(true); + } ); + + // 6.5.4 + clientSendEmptyAuth(client, 'PLAIN'); + // 6.5.5 Server sends a [BASE64] encoded challenge to client: + verifyChallengeTag(socket, ""); + + // 6.5.6: Client sends a [BASE64] encoded response to the challenge: + clientSendResponse( client, getPlainAuthByUserName(userName) ); + + // 6.5.9 - 6.5.11 + xmppAuthSucceed( domain, client, socket ); + params.userName = userName; + return params; + }; + }, + bindUser: function (resourceId) { + return function ( params ) { + var client = params.client; + var socket = params.socket; + + clientSendIqBind( client, resourceId ); + + verifyIqBindResult(socket, params.userName, params.domain, resourceId, client.jid); + return params; + }; + } +}; +//Create a Test Suite +vows.describe('C2S server ').addBatch({ + 'a c2s server created': { + topic : api.createC2S('github.com') , + 'c2s client connected': { + topic : api.createC2SClient( ) , + "c2s client close" : function( params ) { + var server = params.server; + var client = params.client; + assert.isNotNull( client); + client.emit( 'close', 'client error'); + + } + }, + 'a c2s server created': { + topic : api.createC2S('github.com') , + 'c2s client connected': { + topic : api.createC2SClient( ) , + "retrieve stream from client" : { + topic : api.c2sServerStartStream(), + "testing" : function( params ) + { + + } + } + } + }, + 'SASL negotiation (PLAIN) - shortcut': { + topic : api.createC2S('github.com') , + 'c2s client connected': { + topic : api.createC2SClient( ) , + 'client initiates a stream, and server responds with a stream tag sent to client along with auth features (6.5.1 to 6.5.3)': { + topic: api.c2sServerStartStream(), + "empty auth retrieved (correct encoding) , server sends encoded challenge": api.fastSASLNegotiation() + } + } + }, + 'SASL negotiation (PLAIN)': { + topic : api.createC2S('github.com') , + 'c2s client connected': { + topic : api.createC2SClient( ) , + 'client initiates a stream, and server responds with a stream tag sent to client along with auth features (6.5.1 to 6.5.3)': { + topic: api.c2sServerStartStream(), + // TODO : c2s doesn't support slow sasl negotiation + "empty auth retrieved (correct encoding) , server sends encoded challenge": api.slowSASLNegotiation() + } + } + } +} + + /* + 'a user goes online and offline': { + topic : api.createC2S('github.xom') , + 'adding new client': { + topic : api.createC2SClient( ) , + "client has been auth and online" : { + topic : api.jidOnline( 'hpychan', 'github.xom') , + "remove client" : function( params ) { + var username = 'hpychan'; + var server = params.server; + for (var streamId in server.clients) { + var client = server.clients[streamId]; + client.emit( 'end', client); + //server.on + /*JidOffline( client ); + } + assert.equal( server.getJids().length, 0 ); + // get jids with correct usernames + assert.equal( server.getIncomingClients(username).length, 0 ); + } + + } + } + }, + 'a server goes to prebind state with valid jid': { + topic : api.createC2S('github.xom') , fastSslowSASLNegotiationASLNegotiation + 'adding new client': { + topic : api.createC2SClient( ) , + "prebind for jid name" : function( params ) { + var username = 'hpychan'; + var domain = 'github.xom'; + var server = params.server; + for (var streamId in server.clients) { + var client = server.clients[streamId]; + var jid = new xmpp.JID( username, domain ); + var prebind = { 'jid' : jid }; + + client.emit( 'preBind', prebind); + var pattern = 'generatedId-(0-9)*'; + var regEx = new RegExp(pattern,'g'); + assert.isTrue( regEx.test( prebind.resource )); + } + } + } + }, + 'a server goes to prebind state without valid jid': { + topic : api.createC2S('github.xom') , + 'adding new client': { + topic : api.createC2SClient( ) , + "prebind for jid name" : function( params ) { + var username = 'hpychan'; + var domain = 'github.xom'; + var server = params.server; + for (var streamId in server.clients) { + var client = server.clients[streamId]; + var jid = new xmpp.JID( username, domain ); + var prebind = {}; + + client.emit( 'preBind', prebind); + + assert.equal ( prebind.error, 'bad-request' ); + } + } + } + }, + 'a server has error after client connected': { + topic : api.createC2S('github.xom') , + 'adding new client': { + topic : api.createC2SClient( ) , + "error happened" : function( params ) { + var server = params.server; + for (var streamId in server.clients) { + var client = server.clients[streamId]; + + client.emit( 'error', 'client error'); + } + } + } + }, + 'a server has error after client connected and sent a message': { + topic : api.createC2S('github.xom') , + 'adding new client': { + topic : api.createC2SClient( ) , + "error happened after last message sent" : function( params ) { + var server = params.server; + for (var streamId in server.clients) { + var client = server.clients[streamId]; + client.lastMessage = 'hello world'; + client.emit( 'error', 'client error'); + } + } + } + },authe + 'a user goes online, server send a message to the user': { + topic : api.createC2S('github.xom') , + 'adding new client': { + topic : api.createC2SClient( ) , + "client has been auth and online" : { + topic : api.jidOnline( 'hpychan', 'github.xom') , + "send message to user" : { + topic: function( params ) { + var username = 'hpychan'; + var server = params.server; + + server.sendMessage( username, "hello", this.callback ); + }, + "callback from server" : function( statusCode, statusDescription) { + assert.equal( statusCode, 200 ); + assert.equal( statusDescription, "OK" ); + } + } + } + } + }, + 'server send a message to offline user': { + topic : api.createC2S('github.xom') , + "send message to user" : { + topic: function( params ) { + var username = 'hpychan'; + var server = params.server; + + server.sendMessage( username, "hello", this.callback ); + }, + "callback from server" : function( statusCode, statusDescription) { + assert.equal( statusCode, 400 ); + assert.equal( statusDescription, "Not Found" ); + } + } + }, + 'a user goes online, server send a iq message to the user': { + topic : api.createC2S('github.xom') , + 'adding new client': { + topic : api.createC2SClient( ) , + "client has been auth and online" : { + topic : api.jidOnline( 'hpychan', 'github.xom') , + "send iq message to user" : { + + topic: function( params ) { + var username = 'hpychan'; + var server = params.server; + + server.sendIq( username, NS_JABBER_IQ_ROSTER, "query", {}, this.callback ); + }, + "callback from server" : function( params, statusCode, statusDescription, details) { + assert.equal( statusCode, 400 ); + assert.equal( statusDescription, "Not Found" ); + } + } + } + } + }, + 'server send a iq message to offline user': { + topic : api.createC2S('github.xom') , + "send iq message to offline user" : { + topic: function( params ) { + var username = 'hpychan'; + var server = params.server; + + server.sendIq( username, NS_JABBER_IQ_ROSTER, "query", {}, this.callback ); + }, + "callback from server" : function( params, statusCode, statusDescription, details) { + assert.equal( statusCode, 400 ); + assert.equal( statusDescription, "Not Found" ); + } + } + } + */ +}).export( module, {'error': false} ); // export to suite \ No newline at end of file diff --git a/test/test_jid.js b/test/test_jid.js index 15f805b64..e2033b8b8 100644 --- a/test/test_jid.js +++ b/test/test_jid.js @@ -1,6 +1,6 @@ var vows = require('vows'), assert = require('assert'), -xmpp = require('./../lib/xmpp'); +xmpp = require('xmpp'); vows.describe('JID').addBatch({ @@ -97,4 +97,4 @@ vows.describe('JID').addBatch({ } } -}).run(); +}).export( module );