Tutorial - Self hosted Conference Mapper API

For this tutorial , I’m starting from the basis that you already have Jitsi’s API working with your system and simply want to self host , moving away from dependency on https://jitsi-api.jitsi.net/

First, an overview of how the API works -

When users initiate a conference, their browser sends an http request to the API defined in /etc/jitsi/meet/your.domain-config.js > dialInConfCodeUrl . The request is sent in the following format

https://jitsi-api.jitsi.net/conferenceMapper?conference=newmeeting1234@conference.meet.domain.com

The API returns a string that looks like this:


{"message":"Successfully retrieved conference mapping","id":245367,"conference":"newmeeting1234@conference.meet.domain.com"}

Further, when an IVR requests meeting information, a similar request is sent with a known id:

https://jitsi-api.jitsi.net/conferenceMapper?id=245367

For the ID request, The API returns a string formatted exactly the same as the conference request:

{"message":"Successfully retrieved conference mapping","id":245367,"conference":"newmeeting1234@conference.meet.domain.com"}

Let’s get started!

We will mimic this process using PHP and MySQL . I will be hosting all elements on my Jitsi system , which is running on Ubuntu 20.04 server.


image
1. Install mySQL


sudo apt-get install mysql-server

  • After install , run the secure install utility

run sudo mysql_secure_installation utility

  • This part will ask security related questions. Personally , I use strong password strength , make a root password, prevent root from remote login, and remove the test table. Just good security practice in my opinion.

  • Optional - Create a remote user to use a GUI on another system

enter the following commands, replacing USERNAME & PASSWORD with your desired credentials:

sudo mysql
CREATE USER 'USERNAME'@'%' IDENTIFIED BY 'PASSWORD';
grant all privileges on *.* TO 'USERNAME'@'%';
FLUSH PRIVILEGES;
\q (to exit)
  • start mySQL:
sudo systemctl start mysql
  • enable start on boot:
sudo systemctl enable mysql
  • edit bind addresses in mysql config
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

ensure you have the following lines (127.0.0.1 should already be there)

bind-address = 127.0.0.1
bind-address = 192.168.1.20 (replace with server's external IP for remote access)
  • restart mysql to pickup the changes
sudo systemctl restart mysql

image

2. Create mysql table This will house the id & conference name data

I’m using MySQL workbench for the actual table creation, connected using the optional remote user created

  • File > new model > Add Table: jitsiapi

  • Create 2 columns: id & conference, use the following settings for each

[ id ]

Datatype: INT
PK – Primary Key
NN - Not Null
UQ - Unique
A - Autoincrement

[ conference ]

Datatype: VARCHAR(255)
NN - Not Null
UQ - Unique
  • to create the table, Database > forward engineer > Click next through the prompts to write the table

Note: By default , mysql will start inserting id’s sequential from 1 (1, 2, 3, 4). since the ID will be our pin, I’d like to start with something a bit larger. Log into mysql from terminal and run the command below. The number 344100 can be whatever you want to start with.

sudo mysql
ALTER TABLE `mydb`.`jitsiapi` AUTO_INCREMENT=344100;

Mysql tips -

  • You’ll probably want to clear out test data at some point. To quickly clear the table run:
TRUNCATE TABLE `mydb`.`jitsiapi`;
  • clearing the table also resets the starting id , so you’ll want to redo the auto increment command:
ALTER TABLE `mydb`.`jitsiapi` AUTO_INCREMENT=344100;
  • if you receive Access denied for user ‘root’@‘localhost’ , log into mysql from terminal and run following command:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'insert_password';

image
3. Install PHP & add nginx config

sudo apt install php-fpm php-mysql
  • modify nginx site config
sudo nano /etc/nginx/sites-available/your.domain.conf
  • Add the following location to the existing SSL Server block

(change “php7.4” to the version you just installed)

location ~ [^/]\.php(/|$) {
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
include snippets/fastcgi-php.conf;
}
  • restart nginx
sudo systemctl restart nginx

4. Upload PHP file

I’m hosting php files from the jitsi-meet/static location. To do this, you need a user with write access. I prefer to acl for permissions.

sudo apt install acl
sudo setfacl -m u:Existing-UserName-Here:rwx /usr/share/jitsi-meet/static
  • now you can use a remote user to add php files via FTP (I use filezilla)

  • save the below code as a php file and upload to usr/share/jitsi-meet/static/
    Note: There’s a commented section that restricts the id to 6 digits , change this to suit your needs.

<?php
header('Content-Type: application/json');
//header("Content-Type: text/plain");
$conference = $_REQUEST['conference'];
$id = $_REQUEST['id'];


$db_host = 'localhost';
$db_username = 'root';
$db_password = 'PASSWORD';
$db_name = 'mydb';
$db_table = 'jitsiapi';
$digits = ceil(log10($id));

// Set blank variable if conference contains spaces or is missing "@conference."
if (preg_match('/\s/',$conference) > 0) {
    $conference = '';
} else {
    $conference = $conference;
}
if (strpos($conference, '@conference.') !== false) {
    $conference = $conference;
} else {
    $conference = '';
}

// Create connection
$conn = new mysqli($db_host, $db_username, $db_password, $db_name);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}


//Check that ID is numeric & 6 digits in length
if (is_numeric($id) && $digits = 6) {

$sql = $conn->prepare("SELECT id, conference FROM $db_table WHERE id=?");
$sql->bind_param("i", $id);
$sql->execute();
$result = $sql->get_result();

if ($result->num_rows > 0) {
  // output data of each row
  while($row = $result->fetch_assoc()) {
     echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}";

  }
} else {
  echo "{\"message\":\"No conference mapping was found\",\"id\":$id,\"conference\":false}";
}

} else {

$sql = $conn->prepare("SELECT id, conference FROM $db_table WHERE conference=?");
$sql->bind_param("s", $conference);
$sql->execute();
$result = $sql->get_result();

if ($result->num_rows > 0) {
  while($row = $result->fetch_assoc()) {
 echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}";

}
} else {

if ($conference == NULL) { 
echo "{\"message\":\"No conference or id provided\",\"conference\":false,\"id\":false}";
} else {

// Insert Conference
$sql = $conn->prepare("INSERT INTO $db_table (conference) VALUES (?)");
$sql->bind_param("s", $conference);
$sql->execute();
$result = $sql->execute();

// Return resulting data
$sql = $conn->prepare("SELECT id, conference FROM $db_table WHERE conference=?");
$sql->bind_param("s", $conference);
$sql->execute();
$result = $sql->get_result();

while($row = $result->fetch_assoc()) {
 echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}";
}
}
}
}
$conn->close();
?>



5. Provision config.js and IVR

  • Tell jitsi to use your new php script as the API. Replace meet.domain.com with your jitsi domain
sudo nano /etc/jitsi/meet/meet.domain.com-config.js
  • Modify the dialConfCodeULR line to match your new path
dialInConfCodeUrl: 'https://meet.domain.com/static/conferencemapper.php',
  • IVR - update your existibg curl url to match what you used above.

Note: If you’re coming from my IVR tutorial, here’s a modified curl command. I added a pipe to remove new lines if they exist in the result.

exten => s,n(passcode),Set(CURL_RESULT=${SHELL(curl --silent https://meet.domain.com/static/conferencemapper.php?id=${confid} | sed -e 's/.*"conference":"\(.*\)@.*/\1/' | tr -d "\n")})

  1. Pause here and do some testing

At this point, your API should be functioning. Let’s do some testing…

  • Start a web conference and navigate to the share box, check that a pin as populated.

  • Open another web browser or tab and enter the following, replacing with your domain and PIN obtained from the conference.

https://meet.domain.com/static/conferencemapper.php?id=PINHERE

This should respond with text indicating the conference name associated with the ID, formatted like the example below:

{"message":"Successfully retrieved conference mapping","id":245367,"conference":"newmeeting1234@conference.meet.domain.com"}
  • Test the IVR with the same PIN. If the call doesn’t route through to the conference, check your asterisk logs for details.

Trouble shooting tips:

  • If no curl result exists in the log, you may have typed or pasted something wrong in the curl url

  • SSH into your asterisk system and try a curl directly from terminal, confirm it works and check against dial plan

  • If terminal displays an error, lookup the error and try to isolate where the issue is. Also, may be worth trying curl from another system to compare

  • If terminal curl responds “curl: (52) Empty reply from server” , your using an outdated curl package. Check your curl version ( curl –-version ). Anything older than 7.49 doesn’t support http2 , but technically you should be using 7.70 as of May 2020.

4 Likes

Great job…

1 Like

Maybe new PHP versions are different. But Is your code not pretty volnurable to SQL injections? An attacker could take over the SQL Server and maybe further?

Example:
If I put in the “id” parameter
1’; SELECT more blabla; ’

I think you need to escape or clean the parameters first before usage.

@DSchaef Thanks for the feedback. I’m running inline intrusion detection, but one can never be too cautious.

If someone would like the modify the php and share , I’m sure others would like to see your suggestion implemented. Thanks :slight_smile:

Hi Craig,

first of all thanks for your involvement.
then I tried your method. It works but I have one issue. unfortunately a big one) :

I have no sound once the user connected.
where if I commit a call from the session to a phone I have sound in both directions.

May be it’s because of some changes I did but I’m not sure.

Here are the changes I did :

  • As I was already hosting some jitsi plugin files on it, I put the Jitsi_confPin.php on the freePBX server.
  • Instead of autoincrement the id in mysql I generate a 6 digit number in the php and insert it as id in mysql.

that’s all.

So as I say before :

  • I create a new session : OK
  • In the information panel I see the 6 digits number : OK
  • I call FreePBX extension and get access to a working IVR : OK
  • I enter the jitsi session with phone : OK
  • Can communicate in any direction : KO (But got sound in both direction if a call is commit from the conference)

Whereas If I go back to the legacy api : I have sound in both direction.

I can’t understand what’s happening. Any idea?

Sounds like a bad header. I had that problem numerous time as I was building my IVR.

Synopsis:
The IVR should be taking the curl result and setting it as the room header. if the curl result contains the right room name, then call connects. if the header contains the right room name + some extra erroneous characters, like white spaces or carriage returns, you’ll still connect but the headers being exchanged will cause issues. Symptom being no audio in the rtp stream.

If you’re using FreePBX, set your dialplan to display your curl result in the logs using a “verbose” statement. Check the asterisk log for whitespeaces in the curl result. If that’s the case, reconfigure your curl command to remove the white spaces. (If you’re using my IVR Tutorial, the verbose statement is already there… you can just skip to viewing the logs)

Other approach to that could also be figuring out why the white spaces are sent, if that’s what’s happening

Ok, I’ll try that but I’m totally noob in Astrisk or FreePBX.

set your dialplan to display your curl result in the logs using a “verbose” statement.

This is already done in your custom extension script here, no ? :
exten => s,n,Verbose(0, ${CURL_RESULT});

Thanks by advance.

curl and header seem ok no hidden character:

[2020-05-14 15:41:50] VERBOSE[25704][C-00000007] pbx.c: Executing [s@Jitsi-Conference-Entry:16] Set("PJSIP/OVH-Trunk_Seconde_0033973765876-00000006", "CURL_RESULT=siptest")
[2020-05-14 15:41:53] VERBOSE[25704][C-00000007] pbx.c: Executing [s@Jitsi-Conference-Entry:20] SIPAddHeader("PJSIP/OVH-Trunk_Seconde_0033973765876-00000006", "Jitsi-Conference-Room:siptest")

Or may be I did not understand what I’m searching for. As I really am a noob.

You were right, there was an invisible \n

I generate de 6 digits like this :

$id = sprintf("%06d", mt_rand(1, 999999));
$sql = "INSERT INTO $db_table (id,conference) VALUES ('$id','$conference')";

May be the sprintf or the rand is the cause.
Anyway : Thank you it WOOOOORKS !!

1 Like

@DSchaef thanks again for the feedback. I had a little time to fiddle… re-posted the php using prepared statements and also restricted the user inputs. Should help those not running IDS :slight_smile:

This fixed our issue as well. For some reason the original code on our server was creating 2 database entries for every PHP call from jitsi. This resulted in it not displaying any dial-in information in the GUI because the request to get the dial-in code was returning 2 results so the source code reported invalid JSON.
Thank you!