Microsoft Teams for Incredible
PBX®
Licensed for use pursuant to
GPLv2+
Portions Copyright © 2021-2023,
Ward Mundy & Associates LLC
Support: https://wiki.incrediblepbx.com
Add the SBC,
Voice Route, Dial Plan, and Voice Routing Policy
Setup domain
(FQDN) and certificate
Allow the
Microsoft Teams SIP proxies through the Incredible PBX firewall
Setting
up Teams PBX extensions
Appendix 1: VitalPBX patch to PJSIP to support Microsoft Teams
signaling
Appendix 2:
Alternate configuration
This guide will walk through the
steps for setting up Microsoft Teams users as extensions to Incredible PBX. The
guide consists of three major sections:
·
Setup of the Microsoft 365/Teams environment, including
licensing, SBC configuration, and setup of the Direct Routing environment
·
Setup of Incredible PBX trunking and dial plan
·
Adding Teams extensions to Incredible PBX
At the end of the tutorial, you
will be able to use a Teams client (desktop, mobile, or both at once) as an
extension to Incredible PBX that functions almost identically to a SIP phone
extension.
|
Expand the menu on the left, open Settings and go to Domains. Here we add the domain (FQDN) that will be assigned to the PBX or is already assigned. Click Add Domain and type the domain (FQDN) of the PBX. We are using "mypbx.team". Note that you must use a name for which you have DNS control. Dynamic DNS is OK as long as you can also manually add DNS records (see next step). |
|
Use the TXT record method to verify. |
|
Add the DNS record before clicking Verify. |
|
After verification, click Continue. |
|
Deselect the Exchange box. We do not need to add any more records at this time. Click Continue. |
|
|
Switch to the Users - Active Users screen. Before adding the SBC, we have to create an "Activation User" which is a Teams-licensed user that has the same domain as the SBC (PBX). |
|
Be sure to pick the PBX's domain name for this user. This user will never log in, so do not worry about password details. |
|
The user must be assigned a license with Teams (MS 365 / Office 365). TIP: if you are doing this in an established Microsoft tenant, you can temporarily unassign a license from another user and assign it to the Activation User. The license can be unassigned from this user once the SBC setup is done. Do not assign a Phone System license. Click Next until finished. |
|
The next steps are performed in Microsoft Teams administration. Go to https://admin.teams.microsoft.com and log in with your Global Admin user (the one you created at initial sign-up). Expand the menu on the left, go to Voice, and Direct Routing. Click + Add under SBCs. |
|
Specify the details for the SBC. Enter the FQDN (the domain name you just set up), toggle Enabled to On, set the signaling port to 5061, enable SIP Options, enable PAI, and set a reasonable concurrent call capacity. Click Save. |
|
After saving your new SBC, you will be back at the Direct Routing screen and see your SBC listed. After setting up the PBX, we will check back here to make sure the SBC is in a healthy state. |
|
Now go to the Voice Routes tab on this screen and click LocalRoute to edit it. |
|
Change the Dialed number pattern to .* This pattern accepts any digits, *, or # that are dialed; any dial string will be routed to our PBX. Click Add SBCs and pick your SBC. |
|
Click Add PSTN Usage, create a new one and name it whatever you like, select it, and save & apply. |
|
Lastly, save the voice route and you are returned to the Direct Routing screen. |
|
Navigate to Dial Plans and edit Global. |
|
Click Add to add a Normalization Rule. Call it "No Modification", use the Advanced options, and enter the regex ^(.*)$. Set the translation to $1 . The reason for this rule is that by default, Teams will apply transformations related to your E.164 region. We do not want any modifications done on the dialed numbers; they should be passed through, exactly as dialed, to our PBX. |
|
After saving the Normalization Rule, save the Dial Plan. |
|
Navigate to Voice Routing Policies; add a new one. Name it MyVoiceRoutingPolicy and click the button to add your PSTN Usage Record, created earlier. Click Save. |
We will use the Asterisk PJSIP
code modifications from VitalPBX, released to the
public per GPL licensing requirements, to enable Asterisk to communicate with
Microsoft's SIP proxies. The patch is included in appendix 1 of this document,
originally from this Github commit:
https://github.com/asterisk/asterisk/commit/203b6ebb9b976a496e524fc41b3b4f2b7480fff1
This patch has been tested
against Asterisk 20.2.1 and 20.3.0 using the latest release of Incredible PBX
2027.
To include this patch with
Incredible PBX 2027 with Asterisk 20, issue the following commands after
logging into your server as root:
cd /root
wget http://incrediblepbx.com/upgrade-asterisk20T
chmod
+x upgrade-asterisk20T
./upgrade-asterisk20T
Configure the DNS A record for
your PBX if you have not done so already. We are using mypbx.team as the domain name configured earlier for
Teams and which will point to our PBX.
mypbx.team → A → x.x.x.x
In Incredible PBX, go to Admin →
Certificate Management:
·
New Certificate → Generate Let's Encrypt
·
Certificate Host Name: mypbx.team
(our example)
·
Briefly open the firewall during certificate generation:
·
iptables -I INPUT -m tcp -p tcp --dport
80 -j ACCEPT , click Generate Certificate, when
complete, iptables -D INPUT 1
·
Because of the need for manual firewall manipulation,
automatic renewal will fail. Return to this screen within 90 days to manually
renew the certificate by clicking the edit button next to the Let's Encrypt
certificate and clicking Update (with the firewall temporarily opened).
·
Once generated, click the Default checkbox in the
Certificate Management screen to make this certificate the default
In Asterisk SIP Settings →
General SIP Settings, click Detect Network Address to make sure FreePBX knows about its external IP.
In the SIP Settings [chan_pjsip] tab, pick the certificate you just generated, set
SSL Method to TLSv1.2, and set Verify Client and Verify Server to Yes. Set the
TLS transport to Yes and click Submit.
Go back into the SIP
Settings [chan_pjsip] tab and scroll to the bottom;
confirm that the TLS Port to Listen On is set as 5061.
Click the Apply Config button at
the top. Don't restart Asterisk yet.
Edit /etc/asterisk/pjsip.transports_custom_post.conf to add the MS Teams
domain parameter, substituting in your FQDN:
[0.0.0.0-tls](+)
ms_signaling_address=mypbx.team
Now restart Asterisk with fwconsole restart to bring up the new PJSIP TLS transport with Microsoft
signaling capability.
Verification: check the PJSIP
TLS transport on the asterisk console
# asterisk -r
mypbx-team*CLI> pjsip show transport 0.0.0.0-tls
Transport: <TransportId........> <Type> <cos> <tos> <BindAddress....................>
==========================================================================================
Transport: 0.0.0.0-tls
tls
3 96 0.0.0.0:5061
ParameterName
: ParameterValue
===============================================================
allow_reload
:
true
async_operations
: 1
bind
: 0.0.0.0:5061
ca_list_file
: /etc/ssl/certs/ca-certificates.crt
ca_list_path
:
cert_file
:
/etc/asterisk/keys/mypbx.team.pem
cipher
:
cos
: 3
domain
:
external_media_address
: 1.2.3.4
external_signaling_address : 1.2.3.4
external_signaling_port
: 0
local_net
: 10.0.0.0/255.255.255.0
method
: tlsv1_2
ms_signaling_address
: mypbx.team
password
:
priv_key_file
: /etc/asterisk/keys/mypbx.team.key
protocol
: tls
require_client_cert
: No
symmetric_transport
: false
tos
: 96
verify_client
: Yes
verify_server
: Yes
websocket_write_timeout
: 100
Go to Connectivity → Trunks. Add
a PJSIP Trunk.
In the General tab, name the
trunk "msteams" and then move to the PJSIP
Settings tab.
Apply the following settings:
· Authentication:
None · Registration:
None · SIP Server:
sip.pstnhub.microsoft.com |
· SIP Server
Port: 5061 · Context:
from-teams · Transport:
0.0.0.0-tls |
Move to the Advanced tab
and adjust the following (the rest can remain default):
·
User = Phone =
Yes
·
From Domain =
your domain
·
Server URI =
sip:sip.pstnhub.microsoft.com:5061\;transport=tls
·
Match (Permit) =
sip-all.pstnhub.microsoft.com
·
Rewrite Contact =
Yes
·
Media Encryption =
SRTP via in-SDP
Move to the Codecs tab
and make sure at least ulaw is selected.
Save the trunk and Apply Config.
Use add-fqdn to allow the Microsoft SIP proxies to
send us traffic:
./add-fqdn teamsproxies
sip-all.pstnhub.microsoft.com
Select "2" for SIP
(TCP). This selection covers TLS (a TCP protocol) as well.
Go to Reports → Asterisk Info
and scroll down to PJSIP to verify that endpoint "msteams"
shows status "Avail":
Go to the Teams
administration site (https://admin.teams.microsoft.com), Voice → Direct Routing
and you should now see your SBC showing "Active" in the TLS
connectivity status:
Note that it may take a
few minutes for this status to change. Also, it may take several hours until
you see the SIP Options status change from Warning to Active.
We are using a custom context
for our Teams trunk that removes the + that comes in on our Teams extensions'
caller IDs.
Edit /etc/asterisk/extensions_custom.conf.
At the top or bottom, add the
following short dialplan:
[from-teams]
; strip +
from caller ID coming from Teams
exten => _.,1,Set(CALLERID(num)=${CALLERID(num):1})
same => n,Goto(from-internal,${EXTEN},1)
Save the file and inform
Asterisk of the change with a dialplan reload
command:
asterisk -rx
"dialplan reload"
Go to Applications → Extensions
to add a new extension.
Add a new Custom Extension:
Assign an extension
number and name as usual:
Configure the Voicemail and FM/FM tabs as you
like. In the Advanced tab, set the Dial field to PJSIP/extensionnumber@msteams. For extension 710, it would look like this:
Save the extension and
Apply Config.
Create the user in your
Microsoft tenant, or license an existing user with a Teams license (any MS 365
/ Office 365) and a Phone System license. Your users do not have to have the
same domain name as the SBC/PBX domain; they only have to be within the same
tenant/organization. The new user is added from the admin console at
https://admin.microsoft.com.
Click Next until
finished.
After the user is assigned a
Phone System license, he needs to have "Enterprise Voice" enabled and
the extension number assigned. This is done in Power Shell.
You can use Power Shell from a
Windows computer if you download the Microsoft Teams Power Shell connector.
(See https://docs.microsoft.com/en-us/microsoftteams/teams-powershell-install)
A more convenient way to access Power Shell is through the Azure portal on the
web.
Go to https://portal.azure.com
and log in with your Global Administrator account.
Click the >_ symbol to
the right of the search bar to bring up Cloud Shell, and then choose Power
Shell.
If you don't have an
Azure subscription, you will be directed to the following page where you can
click Start Free. Follow the prompts to get through verification and free
account signup.
If prompted to create a
storage location, accept this (free during first month with credit; a penny or
two per month afterward).
Expand the Power Shell window.
Import the Microsoft
Teams module and log in: (the Power Shell prompt will be represented by PS> ; issue the commands as follows)
PS> import-module MicrosoftTeams
PS> Connect-MicrosoftTeams
-usedeviceauthentication
Follow the one-line instruction
that follows in order to log in using a separate browser tab. Proceed through
the prompts until it says "You have signed in…" We use the
"device authentication" method because it works with 2FA logins,
should you decide to enable them.
Back in Power Shell, you will
see your account ID listed and be returned to the prompt.
Issue the following command to
enable your user, replacing the identity field with your user's login ID and
the onpremlineuri field with your user's PBX
extension (set up in the previous section), in the format of tel:extensionnumber:
PS> Set-CsPhoneNunberAssignment -identity "captain@mypbxteam.onmicrosoft.com" -PhoneNumber
“xxx” -PhoneNumberType DirectRouting
You may receive an error like: Set-CsPhoneNumberAssignment:
Management object not found for identity
"captain@mypbxteam.onmicrosoft.com". If
so, wait a little while and try again. There can be propagation delays with the
licensing.
Grant the user the Voice Routing Policy you
set up previously by issuing the Grant-CsOnlineVoiceRoutingPolicy command:
PS> Grant-CsOnlineVoiceRoutingPolicy -identity "captain@mypbxteam.onmicrosoft.com" -policyname "MyVoiceRoutingPolicy"
You are ready to test.
Install Microsoft Teams on desktop
or mobile if you don't already have it and then log in. You can also use Teams
on the web by navigating to https://teams.microsoft.com. Use a
private/incognito window so that you can log in with a different account than
your tenant's Global Administrator.
Navigate to the Calls
tab. You should see a dial pad and your extension number. If you see a screen
without the dial pad, the Microsoft provisioning is still in progress.
The dial pad is not yet
shown
Wait 30 minutes or so and restart Teams.
Microsoft notes that it may take up to 4 hours after Enterprise Voice has been enabled and the Voice Routing
Policy has been applied for the dial pad to appear.
Teams with dial pad
on desktop |
Teams mobile with dial pad |
Use the dial pad to call other PBX
extensions, PSTN numbers, or internal feature codes such as *97. Call to your
Teams extension from a SIP phone. If you have both a desktop and a mobile
client logged in to Teams, both will ring on an incoming call.
Your Teams extension can be
added to ring groups, targeted directly by an Inbound Route, and generally
participate in Incredible PBX functions.
Note: To add a Teams extension
to a ring group, treat it as if you were adding an external number; e.g. add it to the ring group as 710#.
From
203b6ebb9b976a496e524fc41b3b4f2b7480fff1 Mon Sep 17 00:00:00 2001
From:
Jose Rivera <ing.joserivera26@gmail.com>
Date:
Mon, 17 May 2021 14:23:37 -0600
Subject:
[PATCH] Add support for MS Team implementation
The
variable ms_signaling address is now added. This
variable is not stringify nor change from domain to
IP.
---
include/asterisk/res_pjsip.h | 2
++
res/res_pjsip.c
| 6 ++++++
res/res_pjsip/config_transport.c | 32
++++++++++++++++++++++++++++++++
res/res_pjsip_nat.c
| 19 +++++++++++++++++--
4
files changed, 57 insertions(+), 2 deletions(-)
diff
--git a/include/asterisk/res_pjsip.h
b/include/asterisk/res_pjsip.h
index
2020ca8782c..6f155f893af 100644
---
a/include/asterisk/res_pjsip.h
+++
b/include/asterisk/res_pjsip.h
@@
-188,6 +188,8 @@ struct ast_sip_transport {
AST_STRING_FIELD(external_media_address);
/*!
Optional domain to use for messages if provided could not be found */
AST_STRING_FIELD(domain);
+
/*! MS Team Variable */
+
AST_STRING_FIELD(ms_signaling_address);
);
/*!
Type of transport */
enum ast_transport type;
diff
--git a/res/res_pjsip.c b/res/res_pjsip.c
index
775b63f8d1a..b0f2f2268fa 100644
---
a/res/res_pjsip.c
+++
b/res/res_pjsip.c
@@
-1688,6 +1688,9 @@
<configOption name="external_signaling_address">
<synopsis>External
address for SIP signalling</synopsis>
</configOption>
+ <configOption name="ms_signaling_address">
+
<synopsis>MS Team address for SIP signalling</synopsis>
+
</configOption>
<configOption name="external_signaling_port"
default="0">
<synopsis>External
port for SIP signalling</synopsis>
</configOption>
@@
-2512,6 +2515,9 @@
<parameter
name="ExternalSignalingAddress">
<para><xi:include
xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='transport']/configOption[@name='external_signaling_address']/synopsis/node())"/></para>
</parameter>
+ <parameter
name="MSTeamSignalingAddress">
+
<para><xi:include
xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='transport']/configOption[@name='ms_signaling_address']/synopsis/node())"/></para>
+
</parameter>
<parameter
name="ExternalSignalingPort">
<para><xi:include
xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='transport']/configOption[@name='external_signaling_port']/synopsis/node())"/></para>
</parameter>
diff
--git a/res/res_pjsip/config_transport.c
b/res/res_pjsip/config_transport.c
index
830b03832d9..b6a66a1fdaf 100644
---
a/res/res_pjsip/config_transport.c
+++
b/res/res_pjsip/config_transport.c
@@
-953,6 +953,35 @@ static int privkey_file_to_str(const void *obj, const intptr_t *args, char **buf
return
0;
}
+/*!
\brief Custom handler for ms team parameter */
+static
int transport_ms_signaling_address_handler(const struct aco_option *opt,
struct ast_variable *var, void *obj)
+{
+ struct
ast_sip_transport *transport = obj;
+
RAII_VAR(struct ast_sip_transport_state
*, state, find_or_create_temporary_state(transport),
ao2_cleanup);
+
+
if (!state) {
+
return
-1;
+
}
+
+
if (ast_strlen_zero(var->value)) {
+
/* Ignore empty options */
+
return 0;
+
}
+
+ ast_string_field_set(transport, ms_signaling_address,
var->value);
+
+
return 0;
+}
+
+static
int transport_ms_signaling_address_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const
struct ast_sip_transport *transport = obj;
+
+ *buf = ast_strdup(transport->ms_signaling_address);
+
+ return
0;
+}
+
/*!
\brief Custom handler for turning a string protocol into an enum
*/
static
int transport_protocol_handler(const struct aco_option *opt,
struct ast_variable *var, void *obj)
{
@@
-1650,6 +1679,9 @@ int ast_sip_initialize_sorcery_transport(void)
ast_sorcery_object_field_register(sorcery, "transport", "domain",
"", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport,
domain));
ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server",
"", transport_tls_bool_handler, verify_server_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client",
"", transport_tls_bool_handler, verify_client_to_str, NULL, 0, 0);
+
+ ast_sorcery_object_field_register_custom(sorcery, "transport", "ms_signaling_address",
"", transport_ms_signaling_address_handler,
transport_ms_signaling_address_to_str, NULL, 0, 0);
+
ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert",
"", transport_tls_bool_handler, require_client_cert_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(sorcery, "transport", "method",
"", transport_tls_method_handler, tls_method_to_str, NULL, 0, 0);
#if
defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK !=
0
diff
--git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c
index
1b5fdd12603..bbca375aaa8 100644
---
a/res/res_pjsip_nat.c
+++
b/res/res_pjsip_nat.c
@@
-32,6 +32,7 @@
#include
"asterisk/res_pjsip_session.h"
#include
"asterisk/module.h"
#include
"asterisk/acl.h"
+#include
"asterisk/strings.h"
/*!
URI parameter for original host/port */
#define
AST_SIP_X_AST_ORIG_HOST "x-ast-orig-host"
@@
-445,7 +446,14 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
pjsip_method_cmp(&cseq->method,
&pjsip_register_method)) {
/*
We can only rewrite the URI when one is present */
if
(uri || (uri = nat_get_contact_sip_uri(tdata)))
{
- pj_strdup2(tdata->pool, &uri->host,
ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
+
+ /*
Do not stringify the signalling
address when using MS Teams */
+ if
(!ast_strlen_zero(transport->ms_signaling_address)) {
+ pj_strdup2(tdata->pool, &uri->host,
transport->ms_signaling_address);
+ }else{
+ pj_strdup2(tdata->pool, &uri->host,
ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
+ }
+
if
(transport->external_signaling_port) {
uri->port = transport->external_signaling_port;
ast_debug(4,
"Re-wrote Contact URI port to %d\n", uri->port);
@@
-455,7 +463,14 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
/*
Update the via header if relevant */
if
((tdata->msg->type == PJSIP_REQUEST_MSG)
&& (via || (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL)))) {
- pj_strdup2(tdata->pool, &via->sent_by.host,
ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
+
+ /*
Do not stringify the signalling
address when using MS Teams */
+ if
(!ast_strlen_zero(transport->ms_signaling_address)) {
+
pj_strdup2(tdata->pool, &via->sent_by.host, transport->ms_signaling_address);
+
}else{
+
pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
+
}
+
if
(transport->external_signaling_port) {
via->sent_by.port = transport->external_signaling_port;
}
Teams users can use Microsoft's integrated
voicemail service instead of Incredible PBX voicemail. Be sure to enable only
one or the other--not both.
In the set-csuser command, change -hostedvoicemail $false to
-hostedvoicemail $true. The user then sets up voicemail in the Teams client:
|
|
Microsoft voicemail includes speech-to-text
transcription and direct integration with Outlook.