Permalink
Browse files

added RESP-CODES support, started commenting the code

  • Loading branch information...
andris9
andris9 committed Sep 15, 2010
1 parent 6fc7ce0 commit c799c4f35c986a9d676094b8c582c4c58ec7d5e5
Showing with 189 additions and 66 deletions.
  1. +188 −65 n3.js
  2. +1 −1 sasl.js
View
253 n3.js
@@ -1,17 +1,15 @@
-var net = require('net'),
- crypto = require('crypto'),
- fs = require("fs"),
- sasl_methods = require("./sasl").AUTHMethods;
+var net = require('net'), // Enables to start the server
+ crypto = require('crypto'), // TLS, STARTTLS, MD5
+ fs = require("fs"), // Enables to load the certificate keys
+ sasl_methods = require("./sasl").AUTHMethods; // Extensions to the SASL-AUTH
-// POP3 Server
-
/**
* N3
*
* POP3 Server for Node.JS
- * Usage:
*
+ * Usage:
* N3.startServer(port, server_name, AuthStore, MessageStore[, pkFilename][, crtFilename][, useTLS]);
* - port (Number): Port nr to listen, 110 for unencrypted POP3 and 995 for TLS
* - server_name (String): server domain name, ie. "node.ee"
@@ -24,27 +22,93 @@ var net = require('net'),
**/
var N3 = {
+ /**
+ * N3.server_name -> String
+ *
+ * Domain name of the server. Not really important, mainly used for generating
+ * unique tokens (ie: <unique_str@server_name>) and for logging
+ **/
server_name: "localhost",
+ /**
+ * N3.States -> Object
+ *
+ * Constants for different states of the current connection. Every state has
+ * different possibilities, ie. APOP is allowed only in AUTHENTICATION state
+ **/
States:{
AUTHENTICATION: 1,
TRANSACTION:2,
UPDATE: 3
},
+ /**
+ * N3.COUNTER -> Number
+ *
+ * Connection counter, every time a new connection to the server is made, this
+ * number is incremented by 1. Useful for generating connection based unique tokens
+ **/
COUNTER: 0,
+ /**
+ * N3.authMethods -> Object
+ *
+ * Houses different authentication methods for SASL-AUTH as extensions. See
+ * N3.extendAuth for additional information
+ **/
authMethods: {},
+ /**
+ * N3.capabilities -> Object
+ *
+ * Prototype object for individual servers. Contains the items that will
+ * be listed as an answer to the CAPA command. Individual server will add
+ * specific commands to the list by itself. For example if the server is
+ * useing insecure port 110 but supports STARTTLS then it might add STLS
+ * to the list.
+ **/
capabilities: {
- 1: ["UIDL", "USER"], // Add SASL and STLS automatically
+ // AUTHENTICATION
+ 1: ["UIDL", "USER", "RESP-CODES", "AUTH-RESP-CODE"], // Add SASL and STLS automatically
+ // TRANSACTION
2: ["UIDL", "EXPIRE NEVER", "LOGIN-DELAY 0", "IMPLEMENTATION N3 node.js POP3 server"],
+ // UPDATE
3: []
},
+
+ /**
+ * N3.connected_users -> Object
+ *
+ * Keeps a list of all users that currently have a connection. Users are added
+ * as keys with a value of TRUE to the list and deleted when disconnecting
+ *
+ * Login:
+ * N3.connected_users[username] = true;
+ * Logout:
+ * delete N3.connected_users[username]
+ * Check state:
+ * if(N3.connected_users[username]);
+ **/
+ connected_users:{},
+ /**
+ * N3.startServer(port, server_name, AuthStore, MessageStore[, pkFilename][, crtFilename][, useTLS]) -> Boolean
+ * - port (Number): Port nr to listen, 110 for unencrypted POP3 and 995 for TLS
+ * - server_name (String): server domain name, ie. "node.ee"
+ * - AuthStore (Function): Function to authenticate users, see pop3_server.js for example
+ * - MessageStore (Constructor): See messagestore.js or pop3_server.js for example
+ * - pkFilename (String): Path to Private Key for SARTTLS and TLS
+ * - crtFilename (String): Path to server certificate for SARTTLS and TLS
+ * - useTLS (Boolean): use only secure TLS connections
+ *
+ * Creates a N3 server running on specified port. Return TRUE if the
+ * server was started successfully. If pkFilename and crtFilename is not given
+ * then STARTLS and TLS can't be used.
+ **/
startServer: function(port, server_name, auth, MsgStore, pkFilename, crtFilename, useTLS){
- // If cert files are set, add support to STLS
+ // If cert files are set, add support to TLS
+ // generate credentials for secure connections
var privateKey, certificate, credentials = false;
if(pkFilename && crtFilename){
privateKey = fs.readFileSync(pkFilename),
@@ -55,68 +119,117 @@ var N3 = {
});
}
- net.createServer(this.createInstance.bind(
- this, server_name, auth, MsgStore, credentials, useTLS)).listen(port);
- console.log((useTLS?"Secure server":"Server")+" running on port "+port)
+ // try to start the server
+ try{
+ net.createServer(this.createInstance.bind(
+ this, server_name, auth, MsgStore, credentials, useTLS)
+ ).listen(port);
+ console.log((useTLS?"Secure server":"Server")+" running on port "+port)
+ return true;
+ }catch(E){return false; // probably port is already in use}
},
+ /**
+ * N3.createInstance(server_name, auth, MsgStore, credentials, useTLS, socket) -> Object
+ *
+ * Creates a dedicated server instance for every separate connection. Run by
+ * net.createServer after a user tries to connect to the selected port.
+ **/
createInstance: function(server_name, auth, MsgStore, credentials, useTLS, socket){
new this.POP3Server(socket, server_name, auth, MsgStore, credentials, useTLS);
},
- POP3Server: function(socket, server_name, auth, MsgStore, credentials, useTLS){
- this.server_name = server_name || N3.server_name;
- this.socket = socket;
- this.state = N3.States.AUTHENTICATION;
- this.connection_id = ++N3.COUNTER;
- this.UID = this.connection_id + "." + (+new Date());
- this.authCallback = auth;
- this.MsgStore = MsgStore;
- this.credentials = credentials;
- this.connection_secured = false;
-
- // Copy N3 capabilities info into the current object
- this.capabilities = {
- 1: Object.create(N3.capabilities[1]),
- 2: Object.create(N3.capabilities[2]),
- 3: Object.create(N3.capabilities[3])
- }
-
- // announce STARTLS support
- if(!useTLS && credentials){
- this.capabilities[1].unshift("STLS");
- this.capabilities[2].unshift("STLS");
- }
-
- if(useTLS && credentials)
- socket.setSecure(credentials);
-
- console.log("New connection from "+socket.remoteAddress);
- this.response("+OK POP3 Server ready <"+this.UID+"@"+this.server_name+">");
-
- socket.on("data", this.onData.bind(this));
- socket.on("end", this.onEnd.bind(this));
- socket.on("secure", (function(){
- console.log("Secure connection successfully established")
- this.connection_secured = true;
- }).bind(this));
+ /**
+ * N3.extendAUTH(name, action) -> undefined
+ * - name (String): name for the authentication method, will be listed with SASL
+ * - action (Function): Validates the authentication of an user
+ *
+ * Enables extending the SALS AUTH by adding new authentication method.
+ * action gets a parameter authObject and is expected to return TRUE or FALSE
+ * to show if the validation succeeded or not.
+ *
+ * authObject has the following structure:
+ * - wait (Boolean): initially false. If set to TRUE, then the next response from
+ * the client will be forwarded directly back to the function
+ * - user (String): initially false. Set this value with the user name of the logging user
+ * - params (String): Authentication parameters from the client
+ * - history (Array): an array of previous params if .wait was set to TRUE
+ * - n3 (Object): current session object
+ * - check (Function): function to validate the user, has two params:
+ * - user (String): username of the logging user
+ * - pass (Function | String): password or function(pass){return pass==pass}
+ *
+ * See sasl.js for some examples
+ **/
+ extendAUTH: function(name, action){
+ name = name.trim().toUpperCase();
+ this.authMethods[name] = action;
}
}
-N3.extendAUTH = function(name, action){
- name = name.trim().toUpperCase();
- this.authMethods[name] = action;
-};
+/**
+ * new n3.POP3Server(socket, server_name, auth, MsgStore, credentials, useTLS)
+ *
+ * Creates a dedicated server instance for every separate connection. Run by
+ * N3.createInstance after a user tries to connect to the selected port.
+ **/
+N3.POP3Server: function(socket, server_name, auth, MsgStore, credentials, useTLS){
+ this.server_name = server_name || N3.server_name;
+ this.socket = socket;
+ this.state = N3.States.AUTHENTICATION;
+ this.connection_id = ++N3.COUNTER;
+ this.UID = this.connection_id + "." + (+new Date());
+ this.authCallback = auth;
+ this.MsgStore = MsgStore;
+ this.credentials = credentials;
+ this.connection_secured = false;
+
+ // Copy N3 capabilities info into the current object
+ this.capabilities = {
+ 1: Object.create(N3.capabilities[1]),
+ 2: Object.create(N3.capabilities[2]),
+ 3: Object.create(N3.capabilities[3])
+ }
+ // announce STARTLS support
+ if(!useTLS && credentials){
+ this.capabilities[1].unshift("STLS");
+ this.capabilities[2].unshift("STLS");
+ }
+
+ if(useTLS && credentials)
+ socket.setSecure(credentials);
+
+ console.log("New connection from "+socket.remoteAddress);
+ this.response("+OK POP3 Server ready <"+this.UID+"@"+this.server_name+">");
+
+ socket.on("data", this.onData.bind(this));
+ socket.on("end", this.onEnd.bind(this));
+ socket.on("secure", (function(){
+ console.log("Secure connection successfully established")
+ this.connection_secured = true;
+ }).bind(this));
+}
+
+/**
+ * N3.POP3Server#destroy() -> undefined
+ *
+ * Clears the used variables just in case (garbage collector should
+ * do this by itself)
+ **/
N3.POP3Server.prototype.destroy = function(){
if(this.timer)clearTimeout(this.timer);
this.timer = null;
this.socket = null;
this.state = null;
this.authCallback = null;
+ this.user = null;
this.MsgStore = null;
}
+/**
+ *
+ **/
// kill client after 10 min on inactivity
N3.POP3Server.prototype.updateTimeout = function(){
if(this.timer)clearTimeout(this.timer);
@@ -141,8 +254,13 @@ N3.POP3Server.prototype.afterLogin = function(){
var messages = false;
if(typeof this.MsgStore!="function")
return false;
+
+ if(this.user && N3.connected_users[this.user.trim().toLowerCase()])
+ return "-ERR [IN-USE] You already have a POP session running";
+
if(this.user && (messages = new this.MsgStore(this.user))){
this.messages = messages;
+ N3.connected_users[this.user.trim().toLowerCase()] = true;
return true;
}
return false;
@@ -158,7 +276,11 @@ N3.POP3Server.prototype.onEnd = function(data){
if(this.state===null)
return;
this.state = N3.States.UPDATE;
- console.log("Connection closed by remote host\n\n");
+ if(this.user)
+ console.log("Closing: "+this.user)
+ if(this.user && N3.connected_users[this.user.trim().toLowerCase()])
+ delete N3.connected_users[this.user.trim().toLowerCase()];
+ console.log("Connection closed\n\n");
this.socket.end();
this.destroy();
}
@@ -252,7 +374,7 @@ N3.POP3Server.prototype.cmdAUTH = function(auth){
this.response(response);
}else{
this.authObj = false;
- this.response("-ERR Invalid authentication");
+ this.response("-ERR [AUTH] Invalid authentication");
}
}else{
this.authObj = false;
@@ -263,11 +385,11 @@ N3.POP3Server.prototype.cmdAUTH = function(auth){
N3.POP3Server.prototype.cmdDoAUTH = function(){
var response;
this.user = this.authObj.user;
- if(this.afterLogin()){
+ if((response = this.afterLogin())===true){
this.state = N3.States.TRANSACTION;
response = "+OK You are now logged in";
}else{
- response = "-ERR Error with initializing";
+ response = response || "-ERR [SYS] Error with initializing";
}
this.authState = false;
this.authObj = false;
@@ -283,7 +405,7 @@ N3.POP3Server.prototype.cmdAUTHNext = function(params){
if(!response){
this.authState = false;
this.authObj = false;
- return this.response("-ERR Invalid authentication");
+ return this.response("-ERR [AUTH] Invalid authentication");
}
if(this.authObj.wait){
this.authObj.history.push(params);
@@ -325,23 +447,24 @@ N3.POP3Server.prototype.cmdAPOP = function(params){
params = params.split(" ");
var user = params[0] && params[0].trim(),
hash = params[1] && params[1].trim().toLowerCase(),
- salt = "<"+this.UID+"@"+this.server_name+">";
+ salt = "<"+this.UID+"@"+this.server_name+">",
+ response;
if(typeof this.authCallback=="function"){
if(!this.authCallback(user, function(pass){
return md5(salt+pass)==hash;
})){
- return this.response("-ERR Invalid login");
+ return this.response("-ERR [AUTH] Invalid login");
}
}
this.user = user;
- if(this.afterLogin()){
+ if((response = this.afterLogin())===true){
this.state = N3.States.TRANSACTION;
return this.response("+OK You are now logged in");
}else
- return this.response("-ERR Error with initializing");
+ return this.response(response || "-ERR [SYS] Error with initializing");
}
// USER username - Performs basic authentication, PASS follows
@@ -360,20 +483,20 @@ N3.POP3Server.prototype.cmdPASS = function(password){
if(!this.user) return this.response("-ERR USER not yet set");
if(typeof this.authCallback=="function"){
- console.log("authenticating")
if(!this.authCallback(this.user, function(pass){
return pass==password;
})){
delete this.user;
- return this.response("-ERR Invalid login");
+ return this.response("-ERR [AUTH] Invalid login");
}
}
- if(this.afterLogin()){
+ var response;
+ if((response = this.afterLogin())===true){
this.state = N3.States.TRANSACTION;
return this.response("+OK You are now logged in");
}else
- return this.response("-ERR Error with initializing");
+ return this.response(response || "-ERR [SYS] Error with initializing");
}
// TRANSACTION commands
Oops, something went wrong.

0 comments on commit c799c4f

Please sign in to comment.