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

Comments

  • app.js

    require('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;
    
  • 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.xml to remove the domain definition — replace everything with just an empty include

    cat > /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"
    
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!