Freeswitch - How to create registrant in realtime without XML files
Here's a complete setup for authenticating FreeSWITCH SIP users via mod_xml_curl pointing to a Node.js server.
The flow: FreeSWITCH gets a registration → calls your Node.js HTTP endpoint with section=directory → your server returns user XML with credentials → FreeSWITCH authenticates the user.
xml_curl.conf.xml
<configuration name="xml_curl.conf" description="cURL XML Gateway"> <bindings> <binding name="directory" bindings="directory"> <param name="gateway-url" value="http://127.0.0.1:3005/fs_directory"/> <param name="method" value="POST"/> <param name="timeout" value="5"/> </binding> </bindings> </configuration>
app.js
0
Comments
-
app.jsrequire('dotenv').config(); var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); const users = { '1000': { password: 'secret1000', context: 'default' }, '1001': { password: 'secret1001', context: 'default' }, }; app.all('/fs_directory', (req, res) => { console.log('POST Data:', req.body); const body = req.body; const section = Array.isArray(body.section) ? body.section[0] : body.section; const key_name = body.key_name; const key_value = body.key_value; const action = body.action; if (section !== 'directory') { return res.status(200).type('text/xml').send(notFoundXml()); } res.type('text/xml'); // sip_auth request — FreeSWITCH sends user= and domain= directly if (action === 'sip_auth' || body.key === 'id') { const username = body.user || body.sip_auth_username; const domain = body.domain || body.sip_auth_realm; const user = users[username]; if (!user) { console.log(`User not found: ${username}`); return res.status(200).send(notFoundXml()); } console.log(`sip_auth: returning XML for user ${username}@${domain}`); return res.status(200).send(userXml(username, user, domain)); } // domain-level lookup if (key_name === 'name') { return res.status(200).send(domainXml(key_value)); } // explicit user lookup by id if (key_name === 'id') { const username = key_value; const domain = body.domain || key_value; const user = users[username]; if (!user) { console.log(`User not found: ${username}`); return res.status(200).send(notFoundXml()); } console.log(`id lookup: returning XML for user ${username}@${domain}`); return res.status(200).send(userXml(username, user, domain)); } return res.status(200).send(notFoundXml()); }); function userXml(username, { password, context }, domain) { return `<?xml version="1.0" encoding="UTF-8"?> <document type="freeswitch/xml"> <section name="directory"> <domain name="${domain}"> <user id="${username}"> <params> <param name="password" value="${password}"/> <param name="vm-password" value="${password}"/> </params> <variables> <variable name="toll_allow" value="domestic,international,local"/> <variable name="accountcode" value="${username}"/> <variable name="user_context" value="${context}"/> <variable name="effective_caller_id_name" value="Extension ${username}"/> <variable name="effective_caller_id_number" value="${username}"/> <variable name="outbound_caller_id_name" value="\${outbound_caller_name}"/> <variable name="outbound_caller_id_number" value="\${outbound_caller_id}"/> </variables> </user> </domain> </section> </document>`; } function domainXml(domain) { const dialString = '{^^:sip_invite_domain=' + domain + ':presence_id={dialed_user}@' + domain + '}${sofia_contact(*/${dialed_user}@' + domain + ')},${verto_contact(${dialed_user}@' + domain + ')}'; const xml = `<?xml version="1.0" encoding="UTF-8"?> <document type="freeswitch/xml"> <section name="directory"> <domain name="${domain}"> <params> <param name="dial-string" value="${dialString}"/> </params> <variables> <variable name="record_stereo" value="true"/> <variable name="default_gateway" value="\${default_provider}"/> <variable name="default_areacode" value="\${default_areacode}"/> <variable name="transfer_fallback_extension" value="operator"/> </variables> </domain> </section> </document>`; console.log('domainXml response:\n', xml); // <-- add this return xml; } function notFoundXml() { return `<?xml version="1.0" encoding="UTF-8"?> <document type="freeswitch/xml"> <section name="directory"> </section> </document>`; } // catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404)); }); // error handler app.use(function(err, req, res, next) { console.error('Error:', err.message); // add this res.status(403).send("Forbidden"); }); module.exports = app;0 -
check if local directory files exist that are intercepting the user lookup before curl is called:
ls /etc/freeswitch/directory/ ls /etc/freeswitch/directory/default/ # backup first cp -r /etc/freeswitch/directory /etc/freeswitch/directory.bak # remove user files, keep only the folder structure rm /etc/freeswitch/directory/default/*.xml
Edit
default.xmlto remove the domain definition — replace everything with just an empty includecat > /etc/freeswitch/directory/default.xml << 'EOF' <include> </include> EOF
Then reload
fs_cli -x "reload mod_xml_curl" fs_cli -x "reloadxml" fs_cli -x "xml_locate directory user id 1001"
0
Howdy, Stranger!
Categories
- 101 All Categories
- 26 VoIP
- 13 SIP
- 16 asterisk
- 44 Programming
- 1 Nodejs
- 4 javascript
- 19 PHP
- 8 Codeigniter
- 14 database
- 1 UI/UX
- 2 Flutter
- 29 OS
- 27 Linux
- 1 Virtualization
- 1 Android
- 1 Windows
- 2 legal

