Home

Resources

Products

Developers

Main • BuildYateJabberServer

Building a jabber server

This page describes the steps to follow to build a jabber server using Yate

Summary:

Introduction

The following modules are required:

  • Jabber server - Manage jabber streams, route jabber stanzas
  • Jabber server features? - Implements jabber server features: roster, vcard, private data, offline messages and in stream user management (create, delete, modify)
  • Subscriptions? - Store, retrieve, change users' roster
  • Presence - Store presence sent by server's users used to brodcast to subscribed contacts
  • Users? - Users management (create, delete, modify)
  • OpenSSL - TLS implementation used to encrypt streams when needed
  • Zlib compression? - Implements zlib compression used to compress stream data
  • Register - Authenticate users and write users location to database
  • PostgreSQL or MySQL - Database access

NOTES:
* All database queries were used with PostgreSQL

Top

Configure jabber server module

Minimum required configuration for the jabber server is the serviced domain and listeners for server to server and client to server stream connections.
For our example we are going to use example.com as jabber server domain.
The configuration file is jabberserver.conf:

 
[general]
domains=example.com

[listener client]
enable=yes
type=c2s

[listener server]
enable=yes
type=s2s
 

See Jabber server for more configuration options.

Top

Configure jabber server features module

In our configuration users are not allowed to create or delete accounts but they can change their password after succesfull login.
We will set the database account name and queries used to add, update or delete private data, vcard and offline chat.
We'll store maximum 100 offline chat messages for an user (set maxcount=0 to remove offline messages storage limit).
A database stored procedure is used to add/update users private data because we need to update an existing row if already added.
A database stored procedure is used to add an offline chat message because we need it to check if the maximum allowed messages limit is reached.
The configuration file is jbfeatures.conf:

 
[general]
account=jabberserver

[register]
allow_management=no
allow_change=yes

[vcard]
get=SELECT vcard FROM users WHERE username='${username}'
set=UPDATE users SET vcard VALUES ('${vcard}') WHERE username='${username}'

[private_data]
maxcount=100
get=SELECT xml FROM privatedata WHERE username='${username}' AND tag='${tag}' AND xmlns='${xmlns}'
set=SELECT * FROM privatedata_set('${username}', '${tag}', '${xmlns}', '${xml}')
clear_user=DELETE FROM privatedata WHERE username='${username}'

[offline_chat]
get=SELECT * FROM offlinechat WHERE username='${username}' ORDER BY time
add=SELECT * FROM offlinechat_add('${username}', '${xml}', ${time}, ${maxcount})
clear_user=DELETE FROM offlinechat WHERE username='${username}'
 

See Jabber server features? for more configuration options.

Top

Configure the subscriptions module

For this module we must configure the database account name and queries used to retrieve and update users' roster.
Database stored procedures are used to make sure we update existing rows if already added.
The configuration file is subscription.conf:

 
[general]
account=jabberserver
user_roster_load=SELECT users.username, roster.* FROM users LEFT OUTER JOIN roster ON \
 users.username=roster.username WHERE users.username='${username}'
user_roster_delete=DELETE FROM roster WHERE username='${username}'
contact_load=SELECT * FROM roster WHERE username='${username}' AND contact='${contact}'
contact_subscription_set=SELECT * FROM subscription_set('${username}','${contact}','${subscription}')
contact_set=SELECT * FROM roster_set('${username}','${contact}','${name}','${groups}') AS \
 t(username text,contact text,name text,groups text)
contact_set_full=SELECT * FROM roster_set_full('${username}','${contact}','${name}','${groups}','${subscription}') \
 AS t(username text,contact text,name text,groups text,subscription text)
contact_delete=DELETE FROM roster WHERE username='${username}' AND contact='${contact}'
 

See Subscriptions? for more configuration options.

Top

Configure the users module

For this module we need to configure the database account name and user add, update, remove and retrieve queries.
The configuration file is users.conf:

 
[database]
account=jabberserver
add_user=SELECT * FROM user_add('${user}','${password}')
update_user=UPDATE users SET password='${password}' WHERE username='${user}'
remove_user=DELETE FROM users WHERE username='${user}'
select_user=SELECT * FROM users WHERE username='${user}'
 

See Users? for more configuration options.

Top

Configure the openssl module

For this module we need to configure a certificate.
The certificate will be used when users will want to encrypt their connection to the server or another server will want to encrypt the connection to us.
We are using a certificate stored in a file named server.crt. The file must exist in yate configuration directory.
The certificate will be used for the domain example.com and its subdomains (e.g. yate.example.com).
The configuration file is openssl.conf:

 
[server]
enable=yes
domains=example.com,*.example.com
certificate=server.crt
key=
 

See OpenSSL for more configuration options.

Top

Configure the register module

We will set the database account name and queries for user authentication and user location set and delete.
The configuration file is register.conf:

 
[general]
user.auth=yes
user.register=yes
user.unregister=yes

[default]
priority=90
account=jabberserver

[user.auth]
query=SELECT password FROM users WHERE username='${username}'
result=password

[user.register]
query=SELECT * FROM register_set('${username}','${data}')

[user.unregister]
query=DELETE FROM lines WHERE line='${username}' AND location='${data}'
 

See Register for more configuration options.

Top

Database storage and access configuration

Database configuration

Our example configures a connection to a local PostgreSQL database named jabberserver.
The configuration file is pgsqldb.conf:

 
[jabberserver]
host=localhost
port=5432
database=jabberserver
user=postgres
 

See PostgeSQL for more configuration options.

PostgreSQL example database dump

 
--
-- PostgreSQL database dump
--

SET client_encoding = 'UTF8';
SET standard_conforming_strings = off;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET escape_string_warning = off;

--
-- TOC entry 1756 (class 1262 OID 16393)
-- Name: jabberserver; Type: DATABASE; Schema: -; Owner: postgres
--

CREATE DATABASE jabberserver WITH TEMPLATE = template0 ENCODING = 'UTF8';


ALTER DATABASE jabberserver OWNER TO postgres;

\connect jabberserver

SET client_encoding = 'UTF8';
SET standard_conforming_strings = off;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET escape_string_warning = off;

--
-- TOC entry 311 (class 2612 OID 16396)
-- Name: plpgsql; Type: PROCEDURAL LANGUAGE; Schema: -; Owner: postgres
--

CREATE PROCEDURAL LANGUAGE plpgsql;


ALTER PROCEDURAL LANGUAGE plpgsql OWNER TO postgres;

SET search_path = public, pg_catalog;

--
-- TOC entry 20 (class 1255 OID 16397)
-- Dependencies: 311 3
-- Name: offlinechat_add(text, text, integer, integer); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION offlinechat_add(text, text, integer, integer) RETURNS integer
    AS $_$
DECLARE
    _username ALIAS FOR $1;
    _xml ALIAS FOR $2;
    _time ALIAS FOR $3;
    _max ALIAS FOR $4;
    n integer;
BEGIN
    SELECT INTO n COUNT(*) FROM users WHERE username=_username;
    IF n > 0 THEN
	n := 1;
	IF _max > 0 THEN
	    SELECT INTO n COUNT(*) FROM offlinechat WHERE username=_username;
	    IF n < _max THEN
		n := 1;
	    ELSE
		n := 0;
	    END IF;
	END IF;
	IF n = 1 THEN
	    INSERT INTO offlinechat (username,xml,time) VALUES (_username,_xml,_time);
	END IF;
    END IF;
    RETURN n;
END;
$_$
    LANGUAGE plpgsql;


ALTER FUNCTION public.offlinechat_add(text, text, integer, integer) OWNER TO postgres;

--
-- TOC entry 21 (class 1255 OID 16398)
-- Dependencies: 3 311
-- Name: privatedata_set(text, text, text, text); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION privatedata_set(text, text, text, text) RETURNS text
    AS $_$
DECLARE
    _username ALIAS FOR $1;
    _tag ALIAS FOR $2;
    _xmlns ALIAS FOR $3;
    _xml ALIAS FOR $4;
    data record;
BEGIN
    SELECT INTO data xml FROM privatedata WHERE username=_username AND tag=_tag AND xmlns=_xmlns;
    IF not found THEN
	INSERT INTO privatedata (username,tag,xmlns,xml) VALUES (_username,_tag,_xmlns,_xml);
    ELSE
	UPDATE privatedata SET xml=_xml WHERE username=_username AND tag=_tag AND xmlns=_xmlns;
    END IF;
    return '''';
END;
$_$
    LANGUAGE plpgsql;


ALTER FUNCTION public.privatedata_set(text, text, text, text) OWNER TO postgres;

--
-- TOC entry 1759 (class 0 OID 0)
-- Dependencies: 21
-- Name: FUNCTION privatedata_set(text, text, text, text); Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON FUNCTION privatedata_set(text, text, text, text) IS 'Add or update user''s private data';


--
-- TOC entry 22 (class 1255 OID 16399)
-- Dependencies: 3 311
-- Name: register_set(text, text); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION register_set(text, text) RETURNS text
    AS $_$
DECLARE
    _line ALIAS FOR $1;
    _location ALIAS FOR $2;
    data record;
BEGIN
    SELECT INTO data line FROM lines WHERE line=_line AND location=_location;
    IF not found THEN
	INSERT INTO lines (line,location) VALUES (_line,_location);
    ELSE
	UPDATE lines SET location=_location WHERE line=_line;
    END IF;
    return '''';
END;
$_$
    LANGUAGE plpgsql;


ALTER FUNCTION public.register_set(text, text) OWNER TO postgres;

--
-- TOC entry 23 (class 1255 OID 16400)
-- Dependencies: 3 311
-- Name: roster_set(text, text, text, text); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION roster_set(text, text, text, text) RETURNS SETOF record
    AS $_$
DECLARE
    _username ALIAS FOR $1;
    _contact ALIAS FOR $2;
    _name ALIAS FOR $3;
    _groups ALIAS FOR $4;
    data record;
BEGIN
    SELECT INTO data * FROM roster WHERE username=_username AND contact=_contact;
    IF not found THEN
	INSERT INTO roster (username,contact,name,groups) VALUES (_username,_contact,_name,_groups);
    ELSE
	UPDATE roster SET name=_name, groups=_groups WHERE username=_username AND contact=_contact;
    END IF;
    SELECT INTO data username,contact,name,groups FROM roster WHERE username=_username AND contact=_contact;
    return next data;
    return;
END;
$_$
    LANGUAGE plpgsql;


ALTER FUNCTION public.roster_set(text, text, text, text) OWNER TO postgres;

--
-- TOC entry 1760 (class 0 OID 0)
-- Dependencies: 23
-- Name: FUNCTION roster_set(text, text, text, text); Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON FUNCTION roster_set(text, text, text, text) IS 'Add or update a roster item
Return added/updated row';


--
-- TOC entry 24 (class 1255 OID 16401)
-- Dependencies: 311 3
-- Name: roster_set_full(text, text, text, text, text); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION roster_set_full(text, text, text, text, text) RETURNS SETOF record
    AS $_$
DECLARE
    _username ALIAS FOR $1;
    _contact ALIAS FOR $2;
    _name ALIAS FOR $3;
    _groups ALIAS FOR $4;
    _sub ALIAS FOR $5;
    data record;
BEGIN
    SELECT INTO data * FROM roster WHERE username=_username AND contact=_contact;
    IF not found THEN
	INSERT INTO roster (username,contact,name,groups,subscription) VALUES (_username,_contact,_name,_groups,_sub);
    ELSE
	UPDATE roster SET name=_name, groups=_groups, subscription=_sub WHERE username=_username AND contact=_contact;
    END IF;
    SELECT INTO data username,contact,name,groups,subscription FROM roster WHERE username=_username AND contact=_contact;
    return next data;
    return;
END;
$_$
    LANGUAGE plpgsql;


ALTER FUNCTION public.roster_set_full(text, text, text, text, text) OWNER TO postgres;

--
-- TOC entry 1761 (class 0 OID 0)
-- Dependencies: 24
-- Name: FUNCTION roster_set_full(text, text, text, text, text); Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON FUNCTION roster_set_full(text, text, text, text, text) IS 'Fully add or update a roster item (including subscription). 
Return added/updated row
';


--
-- TOC entry 25 (class 1255 OID 16402)
-- Dependencies: 311 3
-- Name: subscription_set(text, text, text); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION subscription_set(text, text, text) RETURNS text
    AS $_$
DECLARE
    _username ALIAS FOR $1;
    _contact ALIAS FOR $2;
    _sub ALIAS FOR $3;
    data record;
BEGIN
    SELECT INTO data * FROM roster WHERE username=_username AND contact=_contact;
    IF not found THEN
	INSERT INTO roster (username,contact,subscription) VALUES (_username,_contact,_sub);
    ELSE
	UPDATE roster SET subscription=_sub WHERE username=_username AND contact=_contact;
    END IF;
    return '''';
END;
$_$
    LANGUAGE plpgsql;


ALTER FUNCTION public.subscription_set(text, text, text) OWNER TO postgres;

--
-- TOC entry 1762 (class 0 OID 0)
-- Dependencies: 25
-- Name: FUNCTION subscription_set(text, text, text); Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON FUNCTION subscription_set(text, text, text) IS 'Add or update a roster item subscription.
Return added/updated row
';


--
-- TOC entry 26 (class 1255 OID 16403)
-- Dependencies: 3 311
-- Name: user_add(text, text); Type: FUNCTION; Schema: public; Owner: postgres
--

CREATE FUNCTION user_add(text, text) RETURNS integer
    AS $_$
DECLARE
    _username ALIAS FOR $1;
    _password ALIAS FOR $2;
    n integer;
BEGIN
    SELECT INTO n COUNT(*) FROM users WHERE username=_username;
    IF n > 0 THEN
	n := 0;
    ELSE
	INSERT INTO users (username,password) VALUES (_username,_password);
	n := 1;
    END IF;
    RETURN n;
END;
$_$
    LANGUAGE plpgsql;


ALTER FUNCTION public.user_add(text, text) OWNER TO postgres;

SET default_tablespace = '';

SET default_with_oids = true;

--
-- TOC entry 1483 (class 1259 OID 16410)
-- Dependencies: 3
-- Name: lines; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 
--

CREATE TABLE lines (
    line text,
    location text
);


ALTER TABLE public.lines OWNER TO postgres;

--
-- TOC entry 1763 (class 0 OID 0)
-- Dependencies: 1483
-- Name: COLUMN lines.location; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN lines.location IS 'User location';


--
-- TOC entry 1484 (class 1259 OID 16416)
-- Dependencies: 3
-- Name: offlinechat; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 
--

CREATE TABLE offlinechat (
    username text NOT NULL,
    xml text NOT NULL,
    "time" integer NOT NULL
);


ALTER TABLE public.offlinechat OWNER TO postgres;

--
-- TOC entry 1764 (class 0 OID 0)
-- Dependencies: 1484
-- Name: TABLE offlinechat; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON TABLE offlinechat IS 'Store users offline chat';


--
-- TOC entry 1765 (class 0 OID 0)
-- Dependencies: 1484
-- Name: COLUMN offlinechat.xml; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN offlinechat.xml IS 'Chat';


--
-- TOC entry 1766 (class 0 OID 0)
-- Dependencies: 1484
-- Name: COLUMN offlinechat."time"; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN offlinechat."time" IS 'The time the message was receievd by the server';


--
-- TOC entry 1485 (class 1259 OID 16428)
-- Dependencies: 3
-- Name: privatedata; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 
--

CREATE TABLE privatedata (
    username text NOT NULL,
    tag text NOT NULL,
    xmlns text NOT NULL,
    xml text NOT NULL
);


ALTER TABLE public.privatedata OWNER TO postgres;

--
-- TOC entry 1767 (class 0 OID 0)
-- Dependencies: 1485
-- Name: TABLE privatedata; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON TABLE privatedata IS 'Users private data';


--
-- TOC entry 1768 (class 0 OID 0)
-- Dependencies: 1485
-- Name: COLUMN privatedata.username; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN privatedata.username IS 'User owning the private data';


--
-- TOC entry 1769 (class 0 OID 0)
-- Dependencies: 1485
-- Name: COLUMN privatedata.tag; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN privatedata.tag IS 'XML tag';


--
-- TOC entry 1770 (class 0 OID 0)
-- Dependencies: 1485
-- Name: COLUMN privatedata.xmlns; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN privatedata.xmlns IS 'XMPP namespace';


--
-- TOC entry 1771 (class 0 OID 0)
-- Dependencies: 1485
-- Name: COLUMN privatedata.xml; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN privatedata.xml IS 'Private XML data';


--
-- TOC entry 1486 (class 1259 OID 16434)
-- Dependencies: 3
-- Name: roster; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 
--

CREATE TABLE roster (
    username text NOT NULL,
    contact text NOT NULL,
    subscription text,
    name text,
    groups text
);


ALTER TABLE public.roster OWNER TO postgres;

--
-- TOC entry 1772 (class 0 OID 0)
-- Dependencies: 1486
-- Name: TABLE roster; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON TABLE roster IS 'Users roster';


--
-- TOC entry 1487 (class 1259 OID 16440)
-- Dependencies: 3
-- Name: users; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 
--

CREATE TABLE users (
    username text NOT NULL,
    password text,
    vcard text
);


ALTER TABLE public.users OWNER TO postgres;

--
-- TOC entry 1773 (class 0 OID 0)
-- Dependencies: 1487
-- Name: TABLE users; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON TABLE users IS 'Users table';


--
-- TOC entry 1758 (class 0 OID 0)
-- Dependencies: 3
-- Name: public; Type: ACL; Schema: -; Owner: postgres
--

REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM postgres;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC;

--
-- PostgreSQL database dump complete
--
 


Top

Creating jabber users

The examples below will add, change or remove an user gigi@example.com.
This section describes the way users can be created:

  • Use the pgAdmin utility to manually edit the users table
  • Connect to yate using telnet. Issue the following commands:
    • Add: users add gigi@example.com password=some_password
    • Delete: users delete gigi@example.com
    • Change: users update gigi@example.com password=changed_password
  • Any other database access tools and scripts

NOTES:

  • The recommended method is to use the users module
  • If an user is deleted using the users module all its connections to the server will be closed and all related data (roster, private data, offline messages) will be removed.


Top

Generating a certificate

See http://www.openssl.org/docs/HOWTO/certificates.txt for more info on how to create a certificate using the openssl utility.

Top

July 2014:
Yate 5.4 and YateBTS 4 launched. Added JSON and DNS support in Javascript, Handover support in YateBTS.

March 2014:
YateBTS 2.0 launched. Added authentication and WebGUI. Added USSD support in commercial version.

March 2014:
Yate 5.2 launched. Better JavaScript support and a fixed memory leak.

Jan 2014:
YateBTS 1.0 launched. The first GSM Basestation which works with an IMS/VoLTE core network.

Jan 2014:
Yate 5.1 launched. Better JavaScript support and added libygsm. Elisa chatbot added in RManager

Oct 2013:
OpenHSS is the Yate based HLR/HSS solution for MVNO and LTE carriers.

Oct 2013:
Yate 5 released. Added IPv6 support in SIP for LTE. Improved JavaScript support. Download NOW

Jan 2013:
Yate 4.3 released: Added XML support in Javascript. SCCP - GTT routing between different networks. Stability improvements.
Download NOW

Aug 2012:
Yate 4.2 released: SIP flood protection. Better Jabber/Google Voice support. Usable Javascript. Fixed SIGTRAN links fluctuations.
Download NOW

Apr 2012:
YateClient was accepted in the Mac Store.

Yate 4.1 released: better Gvoice support, iSAC codec, support for new Wanpipe drivers. Fixes T.38 and Mac client issues.

Mar 2012:
SS7Cloud is launched today, 1st March, 2012, by NullTeam, Yate creators. Having all you need to be a US CLEC, it brings SS7 services in a cloud.

Feb 2012:
Yate 4.0 released.
SCCP, TCAP, MAP and CAMEL, TCP and TLS in SIP, Javascript fast prototyping of telephony applications and brand new face for YateClient.

Nov 2011:
Here is a video that, quote "demonstrates the truly awesome power of the YATE engine, as it easily handles 3 simultaneous calls to an audio player application including dtmf (button press) handling "(from PaintedRockComm).

Nov 2011:
Yate will attend ORR - OPENRHEINRUHR (November 12 - 13).

04 May 2011:
sipgate chooses open source project Yate for core infrastructure.

12 Apr 2011:
Yate 3.3.2 released.
Fix for Jingle calls to Google Voice dropping after 5 minutes.
4 Apr 2011:
Yate 3.3 released.
Support for GMail chat conference, fixes for internal microphone in MacOS. Minor fixes in SS7 M2PA and ANSI. Fixes in H.323, SIP and RTP.

9 Mar 2011:
Yate 3.2 released.
Bug fixes in SIGTRAN/MGCP/SS7 and added support for CNAM/LNP lookup by SIP INVITE/3xx.

Feb 2011:
Yate will attend FOSDEM and XMPP summit.

31 Jan 2011:
Yate 3.1 released.
Yate client support for Google Voice. Support for any country tones in tonegen.

20 Dec 2010:
Yate 3.0 released.
SS7 ITU certified. SS7 STP added. Client supports Jabber IM (Google Talk + Facebook).

3 May 2010:
Yate 3.0.0 alpha 3 released. Featuring the new Jabber server and wideband audio.

8 March 2010:
Yate 2.2 released. Mostly bug fixes. Dahdi compatible. Latest 2 release before 3.0.

6-7 February 2010:
Yate booth at FOSDEM 2010. Free CD with Freesentral available.

2 Nov 2009:
Yate 2.1 launched. Can replace a Cisco PGW2200 to control a Cisco AS54xx.

6 Aug 2008:
Yate and OpenSIPS (former OpenSER) join to build IP based clusters.

4 Aug 2008:
Yate 2 launched.


EditHistoryBacklinksRecent ChangesSearch