1
0
Fork 0

Added LDAP integration with FollowMe dialplan and inbound lookup

master
Matthieu Lalonde 14 years ago committed by xSmurf
parent 91f906b1ba
commit 5d5986e4dc

4
.gitignore vendored

@ -8,4 +8,6 @@ build/*
.DS_Store .DS_Store
._* ._*
*.*~ *.*~
lab-status.js
provider-balance.js

@ -26,12 +26,12 @@ var load = function (debug) {
var fullPath = __dirname + filePath.substr(1); var fullPath = __dirname + filePath.substr(1);
fileName = filePath.replace(/(\.js)$/, ""); fileName = filePath.replace(/(\.js)$/, "");
fileKey = fileName.replace(/^(.*)\//g, ""); fileKey = fileName.replace(/^(.*)\//g, "");
/*
// Delete module cache // Delete module cache
if (typeof process.mainModule.moduleCache[fullPath] !== "undefined") { if (typeof process.mainModule.moduleCache[fullPath] !== "undefined") {
delete process.mainModule.moduleCache[fullPath]; delete process.mainModule.moduleCache[fullPath];
} }
*/
var configFile = require(fileName).Config; var configFile = require(fileName).Config;
Object.keys(configFile).forEach(function (key) { Object.keys(configFile).forEach(function (key) {

@ -0,0 +1,7 @@
exports.Config = {directory: {
dialString: "{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}",
vmEnabled: true,
userContext: "default",
userGroup: "default",
baseExtension: 10000
}};

@ -3,25 +3,41 @@ var path = require("path");
exports.Config = { exports.Config = {
version: "0.0.1", version: "0.0.1",
responderDir: "responders", inboundDestination: "500 XML default",
gatewayName: "Gateway",
httpd: {
port: 5780,
host: "pbx.example.tld",
color: "yellow"
},
ldap: {
uri: "ldaps://ldap.example.tld/",
users: "ou=people,dc=example,dc=tld?*?sub?",
sipPassword: "AstAccountSecret",
vmPassword: "AstAccountMailbox",
userKey: "uidNumber",
fields: ["uidNumber", "uid", "sn", "givenName", "mobile", "AstAccountSecret", "AstAccountMailbox", "objectClass", "dn"]
},
color: "green", color: "green",
colors: { colors: {
commands: "red", responders: "red",
modules: "green",
configs: "magenta", configs: "magenta",
success: "green", success: "green",
failure: "red" failure: "red"
}, },
responderDir: "responders",
init: function(self) init: function(self)
{ {
if (this.debug === true) { if (this.debug === true) {}
}
/*
if (("fields" in self.ldap) === true) { if (("fields" in self.ldap) === true) {
self.ldap.users.replace(/(\?\*\?)/, self.ldap.fields.join(",")); self.ldap.users.replace(/(\?\*\?)/, self.ldap.fields.join(","));
}*/ }
} }
}; };

@ -1,3 +1,9 @@
/**
* TODO: LDAP query cache for speed and to not hammer the ldap server for nothing 5min cache would be plenty
* TODO: VM Change Password
* TODO: Provider Balance
**/
var debug = true; var debug = true;
var sys = require("sys"), var sys = require("sys"),
@ -5,21 +11,23 @@ var sys = require("sys"),
events = require("events"), events = require("events"),
colors = require("colors"), colors = require("colors"),
path = require("path"), path = require("path"),
hash = require("../deps/node-hash/lib/hash"), hash = require("../modules/node-hash/lib/hash"),
// We load this separately because we might need some things before the configs are loaded // We load this separately because we might need some things before the configs are loaded
FreeNodeConfig = require("../configs/freenode").Config, FreeNodeConfig = require("../configs/freenode").Config,
Configs = require("../configs"), Configs = require("../configs"),
Responders = require("../responders"), Responders = require("../responders"),
LDAPClient = require("../deps/node-ldapsearch/build/default/ldap.node"); LDAPClient = require("../modules/node-ldapsearch/build/default/ldap.node"),
httpServer = require("./httpd").httpServer;
var FreeNode = function() { var FreeNode = function() {
var self = this; var self = this;
this.uptime = (new Date().getTime()); this.uptime = (new Date().getTime());
this.loaders = []; this.loaders = [];
this.ConfigsLoader = null; this.ConfigsLoader = null;
this.RespondersLoader = null; this.RespondersLoader = null;
this.httpd = null;
process.title = "FreeNode"; process.title = "FreeNode";
events.EventEmitter.call(this); events.EventEmitter.call(this);
@ -136,7 +144,9 @@ FreeNode.prototype.reinit = function(cbReturn) {
}; };
FreeNode.prototype.onInited = function() { FreeNode.prototype.onInited = function() {
this.httpServer = new httpd().init();
console.log(util.inspect(this));
}; };
/** /**
@ -160,6 +170,8 @@ FreeNode.prototype.addDependencies = function(reload) {
); );
if (reload === true) { if (reload === true) {
this.httpd.deinit();
// We also want to wait for the MUC to bind // We also want to wait for the MUC to bind
this.addDependency( this.addDependency(
// Loader // Loader
@ -179,6 +191,23 @@ FreeNode.prototype.addDependencies = function(reload) {
Responders.load.apply(self, [self]); Responders.load.apply(self, [self]);
}, "responders:loaded", this] }, "responders:loaded", this]
); );
this.addDependency(
// Loader
[function() {
console.log(("Loading HTTP Server")[Configs.httpd.color]);
self.httpd = new httpServer(self.Responders).init();
return self.httpd;
}, "httpd:binded"],
// Failure
[function(error) {
sys.error(("Error binding HTTP Server")[Configs.colors.failure]);
process.exit(1);
}, "httpd:error"]
);
}; };
/** /**

@ -0,0 +1,154 @@
var sys = require("sys"),
colors = require("colors"),
http = require("http"),
util = require("util"),
querystring = require("querystring"),
events = require("events"),
hash = require("../modules/node-hash/lib/hash"),
dateFormat = require("../modules/date.js").dateFormat,
url = require("url"),
colors = require("colors"),
Configs = require("../configs.js");
var httpServer = function(responders) {
var self = this;
this.httpd = null;
this.responders = responders;
events.EventEmitter.call(this);
};
sys.inherits(httpServer, events.EventEmitter);
exports.httpServer = httpServer;
httpServer.prototype.init = function() {
var self = this;
self.httpd = http.createServer(function(request, response) {
self.handlerResponse(request, response);
})
.addListener("error", function(error) {
console.log("httpd crapped out");
console.log(error);
self.emit("httpd:error", self);
});
self.httpd.listen(Configs.httpd.port);
self.emit("httpd:binded", self);
return self;
};
httpServer.prototype.deinit = function() {
var self = this;
self.httpd.close();
};
httpServer.prototype.getResponder = function(decodedBody) {
var self = this,
responder = null,
handler = null,
selectorLen = 0;
try {
if (typeof decodedBody["section"] === "undefined") {
throw "Invalid Request";
}
if (typeof self.responders[decodedBody["section"].toLowerCase()] === "undefined") {
throw "Invalid Bidings";
} else {
var bindings = decodedBody["section"].toLowerCase();
}
for (entryName in self.responders[bindings]) {
var entry = self.responders[decodedBody["section"]][entryName];
if (typeof entry.selector === "undefined") {
continue;
} else {
for (handlerName in entry.selector) {
if (Object.keys(entry.selector[handlerName]).length > selectorLen) {
var ii = 0;
for (selectorKey in entry.selector[handlerName]) {
var match = false;
switch (typeof entry.selector[handlerName][selectorKey]) {
case "string":
match = (decodedBody[selectorKey] === entry.selector[handlerName][selectorKey]);
break;
case "boolean":
match = (
(entry.selector[handlerName][selectorKey] === true && decodedBody[selectorKey].length !== 0)
|| (entry.selector[handlerName][selectorKey] === false && decodedBody[selectorKey].length === 0)
);
break;
case "object":
case "function":
if (entry.selector[handlerName][selectorKey] instanceof RegExp) {
match = entry.selector[handlerName][selectorKey].test(decodedBody[selectorKey]);
}
break;
}
if (match === true) {
if (++ii === Object.keys(entry.selector[handlerName]).length) {
selectorLen = Object.keys(entry.selector[handlerName]).length;
responder = entry;
handler = handlerName;
}
}
}
}
}
}
}
} catch (err) {
console.log("Invalid request... " + err);
}
console.log(util.inspect(decodedBody));
if (responder !== null) {
console.log(util.inspect(responder));
return [responder, handler];
} else {
return null;
}
};
httpServer.prototype.handlerResponse = function(request, response) {
var self = this,
fullBody = "";
request.on('data', function(chunk) {
// append the current chunk of data to the fullBody variable
fullBody += chunk.toString();
});
request.on('end', function() {
// parse the received body data
var decodedBody = querystring.parse(fullBody);
var responder = null;
if ((responder = self.getResponder(decodedBody)) !== null) {
var cbReturn = function(responseData) {
if (typeof responseData !== "undefined" && responseData !== null && responseData.length > 0) {
response.writeHead(200, "OK", {'Content-Type': 'text/xml'});
response.write(responseData, "ascii");
}
response.end();
};
responder[0][responder[1]](cbReturn, decodedBody);
} else {
response.end();
}
});
};

@ -1,4 +1,4 @@
var FreeNodeHandler = require("./libs/freenode").FreeNode, var FreeNodeHandler = require("./handlers/freenode").FreeNode,
Configs = require("./configs"); Configs = require("./configs");
// Instanciate our main object // Instanciate our main object

@ -2,7 +2,7 @@ var sys = require("sys"),
util = require("util"), util = require("util"),
events = require("events"), events = require("events"),
colors = require("colors"), colors = require("colors"),
recurseDir = require("./deps/recurseDir").recurseDir, recurseDir = require("./modules/recurseDir").recurseDir,
Configs = require("./configs"); Configs = require("./configs");
var load = function (Foulinks) { var load = function (Foulinks) {
@ -34,10 +34,10 @@ var load = function (Foulinks) {
sys.puts(("[ responders ] ." + filePath.replace(__dirname+"/"+Configs.responderDir, "").replace(/(\.js)$/, ""))[Configs.colors.responders]); sys.puts(("[ responders ] ." + filePath.replace(__dirname+"/"+Configs.responderDir, "").replace(/(\.js)$/, ""))[Configs.colors.responders]);
} catch (err) { } catch (err) {
// Don't keep a cache of failed includes! // Don't keep a cache of failed includes!
if (typeof process.mainModule.moduleCache[filePath] !== "undefined") { /*if (typeof process.mainModule.moduleCache[filePath] !== "undefined") {
delete process.mainModule.moduleCache[filePath]; delete process.mainModule.moduleCache[filePath];
} }
*/
delete responderBase[fileKey]; delete responderBase[fileKey];
sys.puts(("[ responders ] ERROR Loading ." + sys.puts(("[ responders ] ERROR Loading ." +
@ -116,12 +116,12 @@ var unload = function(Foulinks, recurse) {
delete this[fileKey]; delete this[fileKey];
} }
/*
// Delete responder cache // Delete responder cache
if (typeof process.mainModule.moduleCache[filePath] !== "undefined") { if (typeof process.mainModule.moduleCache[filePath] !== "undefined") {
delete process.mainModule.moduleCache[filePath]; delete process.mainModule.moduleCache[filePath];
} }
*/
delete this[fileKey]; delete this[fileKey];
sys.puts(("[ responders ] removed: " + fileKey)[Configs.colors.responders]); sys.puts(("[ responders ] removed: " + fileKey)[Configs.colors.responders]);

@ -0,0 +1,46 @@
var sys = require("sys"),
ltx = require("../../modules/ltx/lib/index"),
Configs = require("../../configs.js"),
LDAPClient = require("../../modules/node-ldapsearch/build/default/ldap.node");
var followMe = function(parent) {
this.selector = {
"follow": {"Call-Direction": "inbound", "variable_direction": "inbound", "Caller-Context": "default", "Caller-Destination-Number": /^[1-6]{1}[0-9]{3,6}$/}
};
this.follow = function(cbReturn, decodedBody) {
var searchFilter = "(&("+ Configs.ldap.userKey +"="+ decodedBody["Caller-Destination-Number"] +")(mobile=*))";
var searchUri = Configs.ldap.uri + Configs.ldap.users + searchFilter;
LDAPClient.Search(searchUri, function(error, result) {
if (typeof error !== "undefined") {
cbReturn(null);
} else if (typeof result[0] !== "undefined") {
var response = new ltx.Element("document", {"type":"freeswitch/xml"})
.c("section", {"name":"dialplan", "description": "LDAP Follow Me"})
.c("context", {"name":"default"})
.c("extension", {"name":"inbound_ldap_lookup"})
.c("condition", {"field":"destination_number", "expression":"/^"+ decodedBody["Caller-Destination-Number"] +"$/"})
.c("action", {"application": "set", "data": "hangup_after_bridge=true"}).up()
.c("action", {"application": "set", "data": "continue_on_fail=true"}).up()
.c("action", {"application": "set", "data": "ignore_early_media=true"}).up()
.c("action", {"application": "set", "data": "call_timeout=15"}).up()
.c("action", {"application": "bridge", "data": "sofia/$${domain}/"+ decodedBody["Caller-Destination-Number"]}).up()
.c("action", {"application": "set", "data": "call_timeout=15"}).up()
.c("action", {"application": "bridge", "data": "sofia/gateway/"+ Configs.gatewayName +"/"+ result[0]["mobile"][0]}).up()
.c("action", {"application": "answer"}).up()
.c("action", {"application": "sleep", "data": "1000"}).up()
.c("action", {"application": "voicemail", "data": "default $${domain} "+ decodedBody["Caller-Destination-Number"]}).up()
.up()
.up()
.up()
.up();
cbReturn(response.toString());
}
});
};
};
exports.Responder = [followMe];

@ -0,0 +1,38 @@
var sys = require("sys"),
ltx = require("../../modules/ltx/lib/index"),
Configs = require("../../configs.js"),
LDAPClient = require("../../modules/node-ldapsearch/build/default/ldap.node");
var inboundLookup = function(parent) {
this.selector = {
"lookup": {"Call-Direction": "inbound", "variable_direction": "inbound", "Caller-Context": "public"}
};
this.lookup = function(cbReturn, decodedBody) {
var searchFilter = "(mobile="+ decodedBody["Caller-ANI"] +")";
var searchUri = Configs.ldap.uri + Configs.ldap.users + searchFilter;
LDAPClient.Search(searchUri, function(error, result) {
if (typeof error !== "undefined") {
cbReturn(null);
} else if (typeof result[0] !== "undefined") {
var response = new ltx.Element("document", {"type":"freeswitch/xml"})
.c("section", {"name":"dialplan", "description": "Inbound LDAP Lookup"})
.c("context", {"name":"public"})
.c("extension", {"name":"inbound_ldap_lookup"})
.c("condition", {"field":"ani", "expression":"/^"+ decodedBody["Caller-ANI"] +"$/"})
.c("action", {"application": "set", "data": "effective_caller_id_name="+ result[0]["givenName"][0]}).up()
.c("action", {"application": "export", "data": "alert_info=Bellcore-r5"}).up()
.c("action", {"application": "transfer", "data": Configs.inboundDestination}).up()
.up()
.up()
.up()
.up();
cbReturn(response.toString());
}
});
};
};
exports.Responder = [inboundLookup];

@ -0,0 +1,135 @@
var sys = require("sys"),
ltx = require("../../modules/ltx/lib/index"),
Configs = require("../../configs.js"),
LDAPClient = require("../../modules/node-ldapsearch/build/default/ldap.node");
var ldap = function(parent) {
this.selector = {
"startup": {"tag_name": false, "Event-Name": "REQUEST_PARAMS"},
"auth": {"tag_name": "domain", "Event-Name": "REQUEST_PARAMS", "action": "sip_auth", "Event-Calling-Function": "sofia_reg_parse_auth", "user": /^[1-9]{1}[0-9]{3,6}$/},
"mailbox": {"tag_name": "domain", "Event-Name": "GENERAL", "Event-Calling-Function": /^(resolve_id|voicemail_check_main)$/, "user": /^[1-9]{1}[0-9]{3,6}$/},
"user": {"tag_name": "domain", "Event-Name": "REQUEST_PARAMS", "action": "user_call", "Event-Calling-Function": "user_outgoing_channel", "user": /^[1-9]{1}[0-9]{3,6}$/}
};
this.startup = function(cbReturn, decodedBody) {
var response = new ltx.Element("document", {"type":"freeswitch/xml"})
.c("section", {"name":"directory", "description":"Dynamic User Directory"})
.c("domain", {"name":"$${domain}"})
.c("params")
.c("param", {"name":"dial-string", "value": "{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}"}).up()
.up()
.up()
.up();
cbReturn(response.toString());
};
this.auth = function(cbReturn, decodedBody) {
var searchFilter = "("+ Configs.ldap.userKey +"="+ decodedBody["user"] +")";
var searchUri = Configs.ldap.uri + Configs.ldap.users + searchFilter;
LDAPClient.Search(searchUri, function(error, result) {
if (typeof error !== "undefined") {
cbReturn(null);
} else if (typeof result[0] !== "undefined") {
var response = new ltx.Element("document", {"type":"freeswitch/xml"})
.c("section", {"name":"directory", "description":"Dynamic User Directory"})
.c("domain", {"name":"$${domain}"})
.c("params")
.c("param", {"name":"dial-string", "value": "{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}"}).up()
.up()
.c("groups")
.c("group", {"name":"default"})
.c("users")
.c("user", {"id":decodedBody["user"]})
.c("params")
.c("param", {"name":"password", "value":result[0][Configs.ldap.sipPassword][0]}).up()
.c("param", {"name":"vm-password", "value":result[0][Configs.ldap.vmPassword][0]}).up()
.c("param", {"name":"vm-enabled", "value":"true"}).up()
.up()
.c("variables")
.c("variable", {"name":"toll_allow", "value":""}).up()
.c("variable", {"name":"accountcode", "value":decodedBody["user"]}).up()
.c("variable", {"name":"user_context", "value":"default"}).up()
.up()
.up()
.up()
.up()
.up()
.up()
.up();
cbReturn(response.toString());
}
});
};
this.mailbox = function(cbReturn, decodedBody) {
var searchFilter = "("+ Configs.ldap.userKey +"="+ decodedBody["user"] +")";
var searchUri = Configs.ldap.uri + Configs.ldap.users + searchFilter;
LDAPClient.Search(searchUri, function(error, result) {
if (typeof error !== "undefined") {
cbReturn(null);
} else if (typeof result[0] !== "undefined") {
var response = new ltx.Element("document", {"type":"freeswitch/xml"})
.c("section", {"name":"directory", "description":"Dynamic User Directory"})
.c("domain", {"name":"$${domain}"})
.c("params")
.c("param", {"name":"dial-string", "value": "{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}"}).up()
.up()
.c("groups")
.c("group", {"name":"default"})
.c("users")
.c("user", {"id":decodedBody["user"]})
.c("params")
.c("param", {"name":"password", "value":result[0][Configs.ldap.sipPassword][0]}).up()
.c("param", {"name":"vm-password", "value":result[0][Configs.ldap.vmPassword][0]}).up()
.c("param", {"name":"vm-enabled", "value":"true"}).up()
.up()
.up()
.up()
.up()
.up()
.up()
.up();
cbReturn(response.toString());
}
});
};
this.user = function(cbReturn, decodedBody) {
var searchFilter = "("+ Configs.ldap.userKey +"="+ decodedBody["user"] +")";
var searchUri = Configs.ldap.uri + Configs.ldap.users + searchFilter;
LDAPClient.Search(searchUri, function(error, result) {
if (typeof error !== "undefined") {
cbReturn(null);
} else if (typeof result[0] !== "undefined") {
var response = new ltx.Element("document", {"type":"freeswitch/xml"})
.c("section", {"name":"directory", "description":"Dynamic User Directory"})
.c("domain", {"name":"$${domain}"})
.c("params")
.c("param", {"name":"dial-string", "value": "{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}"}).up()
.up()
.c("groups")
.c("group", {"name":"default"})
.c("users")
.c("user", {"id":decodedBody["user"]}).up()
.up()
.up()
.up()
.up()
.up();
cbReturn(response.toString());
}
});
};
};
exports.Responder = [ldap];
Loading…
Cancel
Save