mirror of
https://gitlab.gnome.org/GNOME/calls.git
synced 2024-11-14 20:35:38 +00:00
8b126484cb
Sofia detects a NAT by presence of the "received" parameter in the Via header in
the response to a REGISTER. Sofia will then update the Contact header to use the
IP as reported by the registrar.
The "received" parameter MUST be included in the response according to
https://datatracker.ietf.org/doc/html/rfc3261#section-18.2.1
when the registrar detects a difference between the domain part of the top Via
header and the packet source address but practice has shown that this will not
always be the case.
Addditionally this change allows us to have origins bound to different network
interfaces which would be useful when a registrar can only be accessed through a
VPN.
This also fixes an issue with SDP introduced in
36880c3d34
which was only seen on some SIP
providers:
The session name ("s=") line is not relevant for establishing a connection,
the connection data (c=") line is.
See https://datatracker.ietf.org/doc/html/rfc4566 section 5.3 and 5.7
1551 lines
43 KiB
C
1551 lines
43 KiB
C
/*
|
|
* Copyright (C) 2021 Purism SPC
|
|
*
|
|
* This file is part of Calls.
|
|
*
|
|
* Calls is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Calls is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "CallsSipOrigin"
|
|
|
|
|
|
#include "calls-account.h"
|
|
#include "calls-message-source.h"
|
|
#include "calls-origin.h"
|
|
#include "calls-sip-call.h"
|
|
#include "calls-sip-enums.h"
|
|
#include "calls-sip-origin.h"
|
|
#include "calls-sip-util.h"
|
|
#include "calls-sip-media-manager.h"
|
|
#include "calls-network-watch.h"
|
|
#include "config.h"
|
|
#include "enum-types.h"
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <glib-object.h>
|
|
|
|
#include <sofia-sip/sofia_features.h>
|
|
#include <sofia-sip/nua.h>
|
|
#include <sofia-sip/su_tag.h>
|
|
#include <sofia-sip/su_tag_io.h>
|
|
#include <sofia-sip/sip_util.h>
|
|
#include <sofia-sip/sdp.h>
|
|
|
|
/**
|
|
* SECTION:sip-origin
|
|
* @short_description: A #CallsOrigin for the SIP protocol
|
|
* @Title: CallsSipOrigin
|
|
*
|
|
* #CallsSipOrigin implements the #CallsOriginInterface and is mainly
|
|
* responsible for managing the sofia-sip callbacks, keeping track of #CallsSipCall
|
|
* objects and coordinating with #CallsSipMediaManager.
|
|
*/
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_NAME,
|
|
PROP_ACC_HOST,
|
|
PROP_ACC_USER,
|
|
PROP_ACC_PASSWORD,
|
|
PROP_ACC_DISPLAY_NAME,
|
|
PROP_ACC_PORT,
|
|
PROP_ACC_PROTOCOL,
|
|
PROP_ACC_AUTO_CONNECT,
|
|
PROP_ACC_DIRECT,
|
|
PROP_ACC_LOCAL_PORT,
|
|
PROP_SIP_CONTEXT,
|
|
PROP_ACC_STATE,
|
|
PROP_ACC_ADDRESS,
|
|
PROP_CALLS,
|
|
PROP_COUNTRY_CODE,
|
|
PROP_CAN_TEL,
|
|
PROP_LAST_PROP,
|
|
};
|
|
static GParamSpec *props[PROP_LAST_PROP];
|
|
|
|
static gboolean set_contact_header = FALSE;
|
|
|
|
struct _CallsSipOrigin
|
|
{
|
|
GObject parent_instance;
|
|
|
|
CallsSipContext *ctx;
|
|
nua_t *nua;
|
|
CallsSipHandles *oper;
|
|
char *contact_header; /* Needed for sofia SIP >= 1.13 */
|
|
|
|
/* Direct connection mode is useful for debugging purposes */
|
|
gboolean use_direct_connection;
|
|
|
|
/* Needed to handle shutdown correctly. See sip_callback and dispose method */
|
|
gboolean is_nua_shutdown;
|
|
gboolean is_shutdown_success;
|
|
|
|
CallsAccountState state;
|
|
|
|
CallsSipMediaManager *media_manager;
|
|
|
|
/* Account information */
|
|
char *host;
|
|
char *user;
|
|
char *password;
|
|
char *display_name;
|
|
gint port;
|
|
char *transport_protocol;
|
|
gboolean auto_connect;
|
|
gboolean direct_mode;
|
|
gboolean can_tel;
|
|
|
|
char *own_ip;
|
|
gint local_port;
|
|
|
|
const char *protocol_prefix;
|
|
char *address;
|
|
char *name;
|
|
|
|
GList *calls;
|
|
GHashTable *call_handles;
|
|
};
|
|
|
|
static void calls_sip_origin_message_source_interface_init (CallsOriginInterface *iface);
|
|
static void calls_sip_origin_origin_interface_init (CallsOriginInterface *iface);
|
|
static void calls_sip_origin_accounts_interface_init (CallsAccountInterface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (CallsSipOrigin, calls_sip_origin, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
|
calls_sip_origin_message_source_interface_init)
|
|
G_IMPLEMENT_INTERFACE (CALLS_TYPE_ORIGIN,
|
|
calls_sip_origin_origin_interface_init)
|
|
G_IMPLEMENT_INTERFACE (CALLS_TYPE_ACCOUNT,
|
|
calls_sip_origin_accounts_interface_init))
|
|
|
|
static void
|
|
remove_call (CallsSipOrigin *self,
|
|
CallsCall *call,
|
|
const gchar *reason)
|
|
{
|
|
CallsOrigin *origin;
|
|
CallsSipCall *sip_call;
|
|
gboolean inbound;
|
|
nua_handle_t *nh;
|
|
|
|
origin = CALLS_ORIGIN (self);
|
|
sip_call = CALLS_SIP_CALL (call);
|
|
|
|
self->calls = g_list_remove (self->calls, call);
|
|
|
|
g_object_get (sip_call,
|
|
"inbound", &inbound,
|
|
"nua-handle", &nh,
|
|
NULL);
|
|
|
|
/* TODO support multiple simultaneous calls */
|
|
if (self->oper->call_handle == nh)
|
|
self->oper->call_handle = NULL;
|
|
|
|
g_signal_emit_by_name (origin, "call-removed", call, reason);
|
|
g_object_unref (call);
|
|
}
|
|
|
|
|
|
static void
|
|
remove_calls (CallsSipOrigin *self,
|
|
const gchar *reason)
|
|
{
|
|
CallsCall *call;
|
|
GList *next;
|
|
|
|
while (self->calls != NULL) {
|
|
call = self->calls->data;
|
|
next = self->calls->next;
|
|
|
|
calls_call_hang_up (call);
|
|
|
|
g_list_free_1 (self->calls);
|
|
self->calls = next;
|
|
|
|
g_signal_emit_by_name (self, "call-removed", call, reason);
|
|
g_object_unref (call);
|
|
}
|
|
|
|
g_hash_table_remove_all (self->call_handles);
|
|
|
|
g_clear_pointer (&self->oper->call_handle, nua_handle_unref);
|
|
}
|
|
|
|
|
|
static void
|
|
on_call_state_changed (CallsSipOrigin *self,
|
|
CallsCallState new_state,
|
|
CallsCallState old_state,
|
|
CallsCall *call)
|
|
{
|
|
g_assert (CALLS_IS_SIP_ORIGIN (self));
|
|
g_assert (CALLS_IS_CALL (call));
|
|
|
|
if (new_state != CALLS_CALL_STATE_DISCONNECTED)
|
|
{
|
|
return;
|
|
}
|
|
|
|
remove_call (self, call, "Disconnected");
|
|
}
|
|
|
|
|
|
static void
|
|
add_call (CallsSipOrigin *self,
|
|
const gchar *address,
|
|
gboolean inbound,
|
|
nua_handle_t *handle)
|
|
{
|
|
CallsSipCall *sip_call;
|
|
CallsCall *call;
|
|
g_autofree gchar *local_sdp = NULL;
|
|
g_auto (GStrv) address_split = NULL;
|
|
const char *call_address = address;
|
|
|
|
/* TODO get free port by creating GSocket and passing that to the pipeline */
|
|
guint local_port = get_port_for_rtp ();
|
|
|
|
if (self->can_tel) {
|
|
address_split = g_strsplit_set (address, ":@;", -1);
|
|
|
|
if (g_strv_length (address_split) >=2 &&
|
|
g_strcmp0 (address_split[2], self->host) == 0)
|
|
call_address = address_split[1];
|
|
}
|
|
|
|
sip_call = calls_sip_call_new (call_address, inbound, self->own_ip, handle);
|
|
g_assert (sip_call != NULL);
|
|
|
|
if (self->oper->call_handle)
|
|
nua_handle_unref (self->oper->call_handle);
|
|
|
|
self->oper->call_handle = handle;
|
|
|
|
self->calls = g_list_append (self->calls, sip_call);
|
|
g_hash_table_insert (self->call_handles, handle, sip_call);
|
|
|
|
call = CALLS_CALL (sip_call);
|
|
|
|
g_signal_emit_by_name (CALLS_ORIGIN (self), "call-added", call);
|
|
g_signal_connect_swapped (call, "state-changed",
|
|
G_CALLBACK (on_call_state_changed),
|
|
self);
|
|
|
|
if (!inbound) {
|
|
calls_sip_call_setup_local_media_connection (sip_call, local_port, local_port + 1);
|
|
|
|
local_sdp = calls_sip_media_manager_static_capabilities (self->media_manager,
|
|
self->own_ip,
|
|
local_port,
|
|
FALSE);
|
|
|
|
g_assert (local_sdp);
|
|
|
|
g_debug ("Setting local SDP for outgoing call to %s:\n%s", address, local_sdp);
|
|
|
|
/* TODO handle IPv4 vs IPv6 for nua_invite (SOATAG_TAG) */
|
|
nua_invite (self->oper->call_handle,
|
|
SOATAG_AF (SOA_AF_IP4_IP6),
|
|
SOATAG_USER_SDP_STR (local_sdp),
|
|
SIPTAG_TO_STR (address),
|
|
TAG_IF (!!self->contact_header, SIPTAG_CONTACT_STR (self->contact_header)),
|
|
SOATAG_RTP_SORT (SOA_RTP_SORT_REMOTE),
|
|
SOATAG_RTP_SELECT (SOA_RTP_SELECT_ALL),
|
|
TAG_END ());
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
dial (CallsOrigin *origin,
|
|
const gchar *address)
|
|
{
|
|
CallsSipOrigin *self;
|
|
nua_handle_t *nh;
|
|
g_autofree char *name = NULL;
|
|
g_autofree char *dial_target = NULL;
|
|
|
|
g_assert (CALLS_ORIGIN (origin));
|
|
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
|
|
|
name = calls_origin_get_name (origin);
|
|
|
|
if (!address || !*address) {
|
|
g_warning ("Tried dialing on origin '%s' without an address",
|
|
name);
|
|
return;
|
|
}
|
|
|
|
if (calls_account_get_state (CALLS_ACCOUNT (origin)) != CALLS_ACCOUNT_ONLINE) {
|
|
g_warning ("Tried dialing '%s' on origin '%s', but it's not online",
|
|
address, name);
|
|
return;
|
|
}
|
|
|
|
self = CALLS_SIP_ORIGIN (origin);
|
|
|
|
nh = nua_handle (self->nua, self->oper,
|
|
NUTAG_MEDIA_ENABLE (1),
|
|
SOATAG_ACTIVE_AUDIO (SOA_ACTIVE_SENDRECV),
|
|
TAG_END ());
|
|
|
|
/* Make sure @host is in the dial target */
|
|
if (g_strstr_len (address, -1, "@"))
|
|
dial_target = g_strdup (address);
|
|
else
|
|
dial_target = g_strconcat (address, "@", self->host, NULL);
|
|
|
|
g_debug ("Calling `%s' from origin '%s'", address, name);
|
|
|
|
/* We don't require the user to input the prefix */
|
|
if (!g_str_has_prefix (address, "sip:") &&
|
|
!g_str_has_prefix (address, "sips:")) {
|
|
g_autofree char * target_with_prefix =
|
|
g_strconcat (self->protocol_prefix, ":", dial_target, NULL);
|
|
|
|
add_call (CALLS_SIP_ORIGIN (origin), target_with_prefix, FALSE, nh);
|
|
} else {
|
|
add_call (CALLS_SIP_ORIGIN (origin), dial_target, FALSE, nh);
|
|
}
|
|
}
|
|
|
|
static void
|
|
create_inbound (CallsSipOrigin *self,
|
|
const gchar *address,
|
|
nua_handle_t *handle)
|
|
{
|
|
g_assert (CALLS_IS_SIP_ORIGIN (self));
|
|
g_assert (address != NULL);
|
|
|
|
/* TODO support multiple calls */
|
|
if (self->oper->call_handle)
|
|
nua_handle_unref (self->oper->call_handle);
|
|
|
|
self->oper->call_handle = handle;
|
|
|
|
add_call (self, address, TRUE, handle);
|
|
}
|
|
|
|
|
|
static void
|
|
sip_authenticate (CallsSipOrigin *self,
|
|
nua_handle_t *nh,
|
|
sip_t const *sip)
|
|
{
|
|
const gchar *scheme = NULL;
|
|
const gchar *realm = NULL;
|
|
g_autofree gchar *auth = NULL;
|
|
sip_www_authenticate_t *www_auth = sip->sip_www_authenticate;
|
|
sip_proxy_authenticate_t *proxy_auth = sip->sip_proxy_authenticate;
|
|
|
|
if (www_auth) {
|
|
scheme = www_auth->au_scheme;
|
|
realm = msg_params_find (www_auth->au_params, "realm=");
|
|
}
|
|
else if (proxy_auth) {
|
|
scheme = proxy_auth->au_scheme;
|
|
realm = msg_params_find (proxy_auth->au_params, "realm=");
|
|
}
|
|
else {
|
|
g_warning ("No authentication context found");
|
|
return;
|
|
}
|
|
g_debug ("need to authenticate to realm %s", realm);
|
|
|
|
/* TODO handle authentication to different realms
|
|
* https://source.puri.sm/Librem5/calls/-/issues/266
|
|
*/
|
|
auth = g_strdup_printf ("%s:%s:%s:%s",
|
|
scheme, realm, self->user, self->password);
|
|
nua_authenticate (nh, NUTAG_AUTH (auth), TAG_END ());
|
|
}
|
|
|
|
|
|
static void
|
|
sip_r_invite (int status,
|
|
char const *phrase,
|
|
nua_t *nua,
|
|
CallsSipOrigin *origin,
|
|
nua_handle_t *nh,
|
|
CallsSipHandles *op,
|
|
sip_t const *sip,
|
|
tagi_t tags[])
|
|
{
|
|
g_debug ("response to outgoing INVITE: %03d %s", status, phrase);
|
|
|
|
/* TODO call states (see i_state) */
|
|
if (status == 401 || status == 407) {
|
|
sip_authenticate (origin, nh, sip);
|
|
}
|
|
else if (status == 403) {
|
|
g_warning ("Response to outgoing INVITE: 403 wrong credentials?");
|
|
}
|
|
else if (status == 904) {
|
|
g_warning ("Response to outgoing INVITE: 904 unmatched challenge."
|
|
"Possibly the challenge was already answered?");
|
|
}
|
|
else if (status == 180) {
|
|
}
|
|
else if (status == 100) {
|
|
}
|
|
else if (status == 200) {
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
sip_r_register (int status,
|
|
char const *phrase,
|
|
nua_t *nua,
|
|
CallsSipOrigin *origin,
|
|
nua_handle_t *nh,
|
|
CallsSipHandles *op,
|
|
sip_t const *sip,
|
|
tagi_t tags[])
|
|
{
|
|
g_debug ("response to REGISTER: %03d %s", status, phrase);
|
|
|
|
if (status == 200) {
|
|
g_debug ("REGISTER successful");
|
|
origin->state = CALLS_ACCOUNT_ONLINE;
|
|
nua_get_params (nua, TAG_ANY (), TAG_END());
|
|
|
|
if (sip->sip_contact && sip->sip_contact->m_url && sip->sip_contact->m_url->url_host) {
|
|
g_free (origin->own_ip);
|
|
origin->own_ip = g_strdup (sip->sip_contact->m_url->url_host);
|
|
}
|
|
|
|
} else if (status == 401 || status == 407) {
|
|
sip_authenticate (origin, nh, sip);
|
|
origin->state = CALLS_ACCOUNT_AUTHENTICATING;
|
|
|
|
} else if (status == 403) {
|
|
g_warning ("wrong credentials?");
|
|
origin->state = CALLS_ACCOUNT_AUTHENTICATION_FAILURE;
|
|
|
|
} else if (status == 904) {
|
|
g_warning ("unmatched challenge");
|
|
origin->state = CALLS_ACCOUNT_AUTHENTICATION_FAILURE;
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (origin), props[PROP_ACC_STATE]);
|
|
}
|
|
|
|
|
|
static void
|
|
sip_r_unregister (int status,
|
|
char const *phrase,
|
|
nua_t *nua,
|
|
CallsSipOrigin *origin,
|
|
nua_handle_t *nh,
|
|
CallsSipHandles *op,
|
|
sip_t const *sip,
|
|
tagi_t tags[])
|
|
{
|
|
g_debug ("response to unregistering: %03d %s", status, phrase);
|
|
|
|
if (status == 200) {
|
|
g_debug ("Unregistering successful");
|
|
origin->state = CALLS_ACCOUNT_OFFLINE;
|
|
|
|
} else {
|
|
g_warning ("Unregisterung unsuccessful: %03d %s", status, phrase);
|
|
origin->state = CALLS_ACCOUNT_UNKNOWN_ERROR;
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (origin), props[PROP_ACC_STATE]);
|
|
}
|
|
|
|
|
|
static void
|
|
sip_i_state (int status,
|
|
char const *phrase,
|
|
nua_t *nua,
|
|
CallsSipOrigin *origin,
|
|
nua_handle_t *nh,
|
|
CallsSipHandles *op,
|
|
sip_t const *sip,
|
|
tagi_t tags[])
|
|
{
|
|
const sdp_session_t *r_sdp = NULL;
|
|
const sdp_session_t *l_sdp = NULL;
|
|
gint call_state = nua_callstate_init;
|
|
CallsCallState state;
|
|
CallsSipCall *call;
|
|
int offer_sent = 0;
|
|
int offer_recv = 0;
|
|
int answer_sent = 0;
|
|
int answer_recv = 0;
|
|
|
|
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
|
|
|
g_debug ("The call state has changed: %03d %s", status, phrase);
|
|
|
|
call = g_hash_table_lookup (origin->call_handles, nh);
|
|
|
|
if (call == NULL) {
|
|
g_warning ("No call found for the current handle");
|
|
return;
|
|
}
|
|
|
|
tl_gets (tags,
|
|
SOATAG_LOCAL_SDP_REF (l_sdp),
|
|
SOATAG_REMOTE_SDP_REF (r_sdp),
|
|
NUTAG_CALLSTATE_REF (call_state),
|
|
NUTAG_OFFER_SENT_REF (offer_sent),
|
|
NUTAG_OFFER_RECV_REF (offer_recv),
|
|
NUTAG_ANSWER_SENT_REF (answer_sent),
|
|
NUTAG_ANSWER_RECV_REF (answer_recv),
|
|
TAG_END ());
|
|
|
|
if (status == 503) {
|
|
CALLS_EMIT_MESSAGE (origin, "DNS error", GTK_MESSAGE_ERROR);
|
|
}
|
|
/* XXX making some assumptions about the received SDP message here...
|
|
* namely: rtcp port = rtp port + 1
|
|
*/
|
|
if (r_sdp) {
|
|
g_autoptr (GList) codecs =
|
|
calls_sip_media_manager_get_codecs_from_sdp (origin->media_manager,
|
|
r_sdp->sdp_media);
|
|
const char *session_ip = NULL;
|
|
const char *media_ip = NULL;
|
|
|
|
if (!codecs) {
|
|
g_warning ("No common codecs in SDP. Hanging up");
|
|
calls_call_hang_up (CALLS_CALL (call));
|
|
return;
|
|
}
|
|
|
|
if (r_sdp->sdp_connection && r_sdp->sdp_connection->c_address)
|
|
session_ip = r_sdp->sdp_connection->c_address;
|
|
|
|
if (r_sdp->sdp_media && r_sdp->sdp_media->m_connections &&
|
|
r_sdp->sdp_media->m_connections->c_address)
|
|
media_ip = r_sdp->sdp_media->m_connections->c_address;
|
|
|
|
if (!session_ip && !media_ip) {
|
|
g_warning ("Could not determine IP of remote peer. Hanging up");
|
|
calls_call_hang_up (CALLS_CALL (call));
|
|
return;
|
|
}
|
|
|
|
calls_sip_call_set_codecs (call, codecs);
|
|
calls_sip_call_setup_remote_media_connection (call,
|
|
media_ip ? : session_ip,
|
|
r_sdp->sdp_media->m_port,
|
|
r_sdp->sdp_media->m_port + 1);
|
|
}
|
|
|
|
switch (call_state) {
|
|
case nua_callstate_init:
|
|
return;
|
|
|
|
case nua_callstate_calling:
|
|
state = CALLS_CALL_STATE_DIALING;
|
|
break;
|
|
|
|
case nua_callstate_received:
|
|
state = CALLS_CALL_STATE_INCOMING;
|
|
g_debug ("Call incoming");
|
|
break;
|
|
|
|
case nua_callstate_ready:
|
|
g_debug ("Call ready. Activating media pipeline");
|
|
|
|
calls_sip_call_activate_media (call, TRUE);
|
|
state = CALLS_CALL_STATE_ACTIVE;
|
|
break;
|
|
|
|
case nua_callstate_terminated:
|
|
g_debug ("Call terminated. Deactivating media pipeline");
|
|
|
|
calls_sip_call_activate_media (call, FALSE);
|
|
state = CALLS_CALL_STATE_DISCONNECTED;
|
|
|
|
g_hash_table_remove (origin->call_handles, nh);
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
calls_call_set_state (CALLS_CALL (call), state);
|
|
}
|
|
|
|
|
|
static void
|
|
sip_r_get_params (int status,
|
|
char const *phrase,
|
|
nua_t *nua,
|
|
CallsSipOrigin *origin,
|
|
nua_handle_t *nh,
|
|
CallsSipHandles *op,
|
|
sip_t const *sip,
|
|
tagi_t tags[])
|
|
{
|
|
tagi_t *from = NULL;
|
|
const char *from_str = NULL;
|
|
|
|
g_debug ("response to get_params: %03d %s", status, phrase);
|
|
|
|
if (!set_contact_header)
|
|
return;
|
|
|
|
if ((status != 200) || ((from = tl_find (tags, siptag_from_str)) == NULL)) {
|
|
g_warning ("Could not find 'siptag_from_tag' among the tags");
|
|
return;
|
|
}
|
|
|
|
from_str = (const char *) from->t_value;
|
|
g_free (origin->contact_header);
|
|
origin->contact_header = g_strdup (from_str);
|
|
}
|
|
|
|
|
|
static void
|
|
sip_callback (nua_event_t event,
|
|
int status,
|
|
char const *phrase,
|
|
nua_t *nua,
|
|
nua_magic_t *magic,
|
|
nua_handle_t *nh,
|
|
nua_hmagic_t *hmagic,
|
|
sip_t const *sip,
|
|
tagi_t tags[])
|
|
{
|
|
CallsSipOrigin *origin = CALLS_SIP_ORIGIN (magic);
|
|
CallsSipHandles *op = origin->oper;
|
|
g_autofree gchar * from = NULL;
|
|
|
|
switch (event) {
|
|
case nua_i_invite:
|
|
if (sip->sip_from && sip->sip_from->a_url &&
|
|
sip->sip_from->a_url->url_scheme &&
|
|
sip->sip_from->a_url->url_user &&
|
|
sip->sip_from->a_url->url_host)
|
|
from = g_strconcat (sip->sip_from->a_url->url_scheme, ":",
|
|
sip->sip_from->a_url->url_user, "@",
|
|
sip->sip_from->a_url->url_host, NULL);
|
|
else {
|
|
nua_respond (nh, 400, NULL, TAG_END ());
|
|
g_warning ("invalid incoming INVITE request");
|
|
break;
|
|
}
|
|
|
|
g_debug ("incoming call INVITE: %03d %s from %s", status, phrase, from);
|
|
|
|
/* reject if there already is a ongoing call */
|
|
if (op->call_handle) {
|
|
nua_respond (nh, 486, NULL, TAG_END ());
|
|
g_debug ("Cannot handle more than one call. Rejecting");
|
|
}
|
|
else
|
|
create_inbound (origin, from, nh);
|
|
|
|
break;
|
|
|
|
case nua_r_invite:
|
|
sip_r_invite (status,
|
|
phrase,
|
|
nua,
|
|
origin,
|
|
nh,
|
|
op,
|
|
sip,
|
|
tags);
|
|
break;
|
|
|
|
case nua_i_ack:
|
|
g_debug ("incoming ACK: %03d %s", status, phrase);
|
|
break;
|
|
|
|
case nua_i_bye:
|
|
g_debug ("incoming BYE: %03d %s", status, phrase);
|
|
break;
|
|
|
|
case nua_r_bye:
|
|
g_debug ("response to BYE: %03d %s", status, phrase);
|
|
if (status == 200) {
|
|
CallsSipCall *call =
|
|
CALLS_SIP_CALL (g_hash_table_lookup (origin->call_handles, nh));
|
|
|
|
if (call == NULL) {
|
|
g_warning ("BYE response from an unknown call");
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nua_r_register:
|
|
sip_r_register (status,
|
|
phrase,
|
|
nua,
|
|
origin,
|
|
nh,
|
|
op,
|
|
sip,
|
|
tags);
|
|
break;
|
|
|
|
case nua_r_unregister:
|
|
sip_r_unregister (status,
|
|
phrase,
|
|
nua,
|
|
origin,
|
|
nh,
|
|
op,
|
|
sip,
|
|
tags);
|
|
break;
|
|
|
|
case nua_r_get_params:
|
|
sip_r_get_params (status,
|
|
phrase,
|
|
nua,
|
|
origin,
|
|
nh,
|
|
op,
|
|
sip,
|
|
tags);
|
|
break;
|
|
|
|
case nua_r_set_params:
|
|
g_debug ("response to set_params: %03d %s", status, phrase);
|
|
break;
|
|
|
|
case nua_i_outbound:
|
|
g_debug ("status of outbound engine has changed: %03d %s", status, phrase);
|
|
|
|
if (status == 404) {
|
|
CALLS_EMIT_MESSAGE (origin, "contact not found", GTK_MESSAGE_ERROR);
|
|
|
|
g_warning ("outbound engine changed state to %03d %s", status, phrase);
|
|
}
|
|
|
|
break;
|
|
|
|
case nua_i_state:
|
|
sip_i_state (status,
|
|
phrase,
|
|
nua,
|
|
origin,
|
|
nh,
|
|
op,
|
|
sip,
|
|
tags);
|
|
break;
|
|
|
|
case nua_r_cancel:
|
|
g_debug ("response to CANCEL: %03d %s", status, phrase);
|
|
break;
|
|
|
|
case nua_r_terminate:
|
|
break;
|
|
|
|
case nua_r_shutdown:
|
|
/* see also deinit_sip () */
|
|
g_debug ("response to nua_shutdown: %03d %s", status, phrase);
|
|
if (status == 200) {
|
|
origin->is_nua_shutdown = TRUE;
|
|
origin->is_shutdown_success = TRUE;
|
|
}
|
|
else if (status == 500) {
|
|
origin->is_nua_shutdown = TRUE;
|
|
origin->is_shutdown_success = FALSE;
|
|
}
|
|
break;
|
|
|
|
/* Deprecated events */
|
|
case nua_i_active:
|
|
break;
|
|
case nua_i_terminated:
|
|
break;
|
|
|
|
default:
|
|
/* unknown event -> print out error message */
|
|
g_warning ("unknown event %d: %03d %s",
|
|
event,
|
|
status,
|
|
phrase);
|
|
g_warning ("printing tags");
|
|
tl_print(stdout, "", tags);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static nua_t *
|
|
setup_nua (CallsSipOrigin *self)
|
|
{
|
|
const char *sip_test_env = g_getenv ("CALLS_SIP_TEST");
|
|
nua_t *nua;
|
|
gboolean use_sips = FALSE;
|
|
gboolean use_ipv6 = FALSE; /* TODO make configurable or use DNS to figure out if ipv6 is supported*/
|
|
const char *ipv6_bind = "*";
|
|
const char *ipv4_bind = "0.0.0.0";
|
|
const char *uuid = NULL;
|
|
g_autofree char *urn_uuid = NULL;
|
|
g_autofree char *sip_url = NULL;
|
|
g_autofree char *sips_url = NULL;
|
|
g_autofree char *from_str = NULL;
|
|
|
|
if (!sip_test_env || sip_test_env[0] == '\0') {
|
|
CallsNetworkWatch *nw = calls_network_watch_get_default ();
|
|
if (nw) {
|
|
ipv4_bind = calls_network_watch_get_ipv4 (nw);
|
|
ipv6_bind = calls_network_watch_get_ipv6 (nw);
|
|
}
|
|
}
|
|
|
|
uuid = nua_generate_instance_identifier (self->ctx->home);
|
|
urn_uuid = g_strdup_printf ("urn:uuid:%s", uuid);
|
|
|
|
self->protocol_prefix = get_protocol_prefix (self->transport_protocol);
|
|
|
|
self->address = g_strconcat (self->user, "@", self->host, NULL);
|
|
from_str = g_strconcat (self->protocol_prefix, ":", self->address, NULL);
|
|
|
|
use_sips = check_sips (from_str);
|
|
use_ipv6 = check_ipv6 (self->host);
|
|
|
|
if (self->local_port > 0) {
|
|
sip_url = g_strdup_printf ("sip:%s:%d",
|
|
use_ipv6 ? ipv6_bind : ipv4_bind,
|
|
self->local_port);
|
|
sips_url = g_strdup_printf ("sips:%s:%d",
|
|
use_ipv6 ? ipv6_bind : ipv4_bind,
|
|
self->local_port);
|
|
} else {
|
|
sip_url = g_strdup_printf ("sip:%s:*",
|
|
use_ipv6 ? ipv6_bind : ipv4_bind);
|
|
sips_url = g_strdup_printf ("sips:%s:*",
|
|
use_ipv6 ? ipv6_bind : ipv4_bind);
|
|
}
|
|
|
|
/** For TLS nua_create() will error if NUTAG_URL includes ";transport=tls"
|
|
* In that case NUTAG_SIPS_URL should be used and NUTAG_URL should be as usual
|
|
* Since UDP is the default we only need to append the suffix in the TCP case
|
|
*/
|
|
if (g_ascii_strcasecmp (self->transport_protocol, "TCP") == 0) {
|
|
char *temp = sip_url;
|
|
|
|
sip_url = g_strdup_printf ("%s;transport=%s", temp, self->transport_protocol);
|
|
g_free (temp);
|
|
}
|
|
|
|
|
|
|
|
nua = nua_create (self->ctx->root,
|
|
sip_callback,
|
|
self,
|
|
NUTAG_USER_AGENT (APP_DATA_NAME),
|
|
NUTAG_URL (sip_url),
|
|
TAG_IF (use_sips, NUTAG_SIPS_URL (sips_url)),
|
|
SIPTAG_FROM_STR (from_str),
|
|
NUTAG_ALLOW ("INVITE, ACK, BYE, CANCEL, OPTIONS, UPDATE"),
|
|
NUTAG_SUPPORTED ("replaces, gruu, outbound"),
|
|
NTATAG_MAX_FORWARDS (70),
|
|
NUTAG_ENABLEINVITE (1),
|
|
NUTAG_AUTOANSWER (0),
|
|
NUTAG_AUTOACK (1),
|
|
NUTAG_PATH_ENABLE (0),
|
|
NUTAG_MEDIA_ENABLE (1),
|
|
NUTAG_INSTANCE (urn_uuid),
|
|
TAG_NULL ());
|
|
|
|
return nua;
|
|
}
|
|
|
|
static char *
|
|
get_registrar_url (CallsSipOrigin *self)
|
|
{
|
|
g_assert (CALLS_IS_SIP_ORIGIN (self));
|
|
|
|
if (self->port > 0 && self->port <= 65535)
|
|
return g_strdup_printf ("%s:%s:%d", self->protocol_prefix, self->host, self->port);
|
|
else
|
|
return g_strconcat (self->protocol_prefix, ":", self->host, NULL);
|
|
}
|
|
|
|
static CallsSipHandles *
|
|
setup_sip_handles (CallsSipOrigin *self)
|
|
{
|
|
CallsSipHandles *oper;
|
|
g_autofree char *registrar_url = NULL;
|
|
|
|
g_assert (CALLS_IS_SIP_ORIGIN (self));
|
|
|
|
if (!(oper = su_zalloc (self->ctx->home, sizeof(CallsSipHandles)))) {
|
|
g_warning ("cannot create handle");
|
|
return NULL;
|
|
}
|
|
|
|
oper->context = self->ctx;
|
|
oper->register_handle = nua_handle (self->nua, self->oper,
|
|
SIPTAG_EXPIRES_STR ("180"),
|
|
NUTAG_SUPPORTED ("replaces, outbound, gruu"),
|
|
NUTAG_OUTBOUND ("outbound natify gruuize validate"), // <- janus uses "no-validate"
|
|
NUTAG_M_PARAMS ("user=phone"),
|
|
NUTAG_CALLEE_CAPS (1), /* header parameters based on SDP capabilities and Allow header */
|
|
TAG_END ());
|
|
oper->call_handle = NULL;
|
|
return oper;
|
|
}
|
|
|
|
|
|
static void
|
|
go_online (CallsAccount *account,
|
|
gboolean online)
|
|
{
|
|
CallsSipOrigin *self;
|
|
|
|
g_assert (CALLS_IS_ACCOUNT (account));
|
|
g_assert (CALLS_IS_SIP_ORIGIN (account));
|
|
|
|
self = CALLS_SIP_ORIGIN (account);
|
|
|
|
if (!self->nua) {
|
|
g_warning ("Cannot go online: nua handle not initialized");
|
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACC_STATE]);
|
|
return;
|
|
}
|
|
|
|
if (online) {
|
|
g_autofree char *registrar_url = NULL;
|
|
|
|
if (self->state == CALLS_ACCOUNT_ONLINE)
|
|
return;
|
|
|
|
registrar_url = get_registrar_url (self);
|
|
|
|
nua_register (self->oper->register_handle,
|
|
NUTAG_M_USERNAME (self->user),
|
|
TAG_IF (self->display_name, NUTAG_M_DISPLAY (self->display_name)),
|
|
NUTAG_REGISTRAR (registrar_url),
|
|
TAG_END ());
|
|
|
|
} else {
|
|
if (self->state == CALLS_ACCOUNT_OFFLINE)
|
|
return;
|
|
|
|
nua_unregister (self->oper->register_handle,
|
|
TAG_END ());
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
get_address (CallsAccount *account)
|
|
{
|
|
CallsSipOrigin *self;
|
|
|
|
g_assert (CALLS_IS_ACCOUNT (account));
|
|
g_assert (CALLS_IS_SIP_ORIGIN (account));
|
|
|
|
self = CALLS_SIP_ORIGIN (account);
|
|
|
|
return self->address;
|
|
}
|
|
|
|
|
|
static void
|
|
setup_account_for_direct_connection (CallsSipOrigin *self)
|
|
{
|
|
g_assert (CALLS_IS_SIP_ORIGIN (self));
|
|
|
|
/* honour username, if previously set */
|
|
if (self->user == NULL)
|
|
self->user = g_strdup (g_get_user_name ());
|
|
|
|
g_free (self->host);
|
|
self->host = g_strdup (g_get_host_name ());
|
|
|
|
g_clear_pointer (&self->password, g_free);
|
|
|
|
g_free (self->transport_protocol);
|
|
self->transport_protocol = g_strdup ("UDP");
|
|
|
|
self->protocol_prefix = get_protocol_prefix ("UDP");
|
|
|
|
g_debug ("Account changed:\nuser: %s\nhost: %s",
|
|
self->user, self->host);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
is_account_complete (CallsSipOrigin *self)
|
|
{
|
|
/* we need only need to check for password if needing to authenticate over a proxy/UAS */
|
|
if (self->user == NULL ||
|
|
(!self->use_direct_connection && self->password == NULL) ||
|
|
self->host == NULL ||
|
|
self->transport_protocol == NULL)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
init_sip_account (CallsSipOrigin *self,
|
|
GError **error)
|
|
{
|
|
if (self->use_direct_connection) {
|
|
g_debug ("Direct connection case. Using user and hostname");
|
|
setup_account_for_direct_connection (self);
|
|
}
|
|
|
|
if (!is_account_complete (self)) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Must have completed account setup before calling "
|
|
"init_sip_account (). "
|
|
"Try again when account is setup");
|
|
|
|
self->state = CALLS_ACCOUNT_NO_CREDENTIALS;
|
|
goto err;
|
|
}
|
|
|
|
// setup_nua() and setup_sip_handles() only after account data has been set
|
|
self->nua = setup_nua (self);
|
|
if (self->nua == NULL) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed setting up nua context");
|
|
|
|
self->state = CALLS_ACCOUNT_NULL;
|
|
goto err;
|
|
}
|
|
|
|
self->oper = setup_sip_handles (self);
|
|
if (self->oper == NULL) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"Failed setting operation handles");
|
|
|
|
self->state = CALLS_ACCOUNT_NULL;
|
|
goto err;
|
|
}
|
|
|
|
/* In the case of a direct connection we're immediately good to go */
|
|
if (self->use_direct_connection)
|
|
self->state = CALLS_ACCOUNT_ONLINE;
|
|
else {
|
|
self->state = CALLS_ACCOUNT_OFFLINE;
|
|
|
|
if (self->auto_connect)
|
|
go_online (CALLS_ACCOUNT (self), TRUE);
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACC_STATE]);
|
|
return TRUE;
|
|
|
|
err:
|
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACC_STATE]);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
deinit_sip_account (CallsSipOrigin *self)
|
|
{
|
|
g_assert (CALLS_IS_SIP_ORIGIN (self));
|
|
|
|
if (self->state == CALLS_ACCOUNT_NULL)
|
|
return TRUE;
|
|
|
|
remove_calls (self, NULL);
|
|
|
|
if (self->nua) {
|
|
g_debug ("Clearing any handles");
|
|
g_clear_pointer (&self->oper->register_handle, nua_handle_destroy);
|
|
g_debug ("Requesting nua_shutdown ()");
|
|
self->is_nua_shutdown = FALSE;
|
|
self->is_shutdown_success = FALSE;
|
|
nua_shutdown (self->nua);
|
|
// need to wait for nua_r_shutdown event before calling nua_destroy ()
|
|
while (!self->is_nua_shutdown)
|
|
su_root_step (self->ctx->root, 100);
|
|
|
|
if (!self->is_shutdown_success) {
|
|
g_warning ("nua_shutdown() timed out. Cannot proceed");
|
|
return FALSE;
|
|
}
|
|
g_debug ("nua_shutdown() complete. Destroying nua handle");
|
|
nua_destroy (self->nua);
|
|
self->nua = NULL;
|
|
}
|
|
|
|
self->state = CALLS_ACCOUNT_NULL;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
recreate_sip (CallsSipOrigin *self)
|
|
{
|
|
if (deinit_sip_account (self))
|
|
init_sip_account (self, NULL);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
supports_protocol (CallsOrigin *origin,
|
|
const char *protocol)
|
|
{
|
|
CallsSipOrigin *self;
|
|
g_assert (protocol);
|
|
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
|
|
|
self = CALLS_SIP_ORIGIN (origin);
|
|
|
|
if (g_strcmp0 (protocol, "sip") == 0)
|
|
return TRUE;
|
|
|
|
if (g_strcmp0 (protocol, "sips") == 0)
|
|
return g_strcmp0 (self->protocol_prefix, "sips") == 0;
|
|
|
|
if (g_strcmp0 (protocol, "tel") == 0)
|
|
return self->can_tel;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
update_name (CallsSipOrigin *self)
|
|
{
|
|
g_assert (CALLS_IS_SIP_ORIGIN (self));
|
|
|
|
if (self->display_name && self->display_name[0] != '\0')
|
|
self->name = self->display_name;
|
|
else
|
|
self->name = self->user;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NAME]);
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
CallsSipOrigin *self = CALLS_SIP_ORIGIN (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_ACC_HOST:
|
|
g_free (self->host);
|
|
self->host = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_ACC_DISPLAY_NAME:
|
|
g_free (self->display_name);
|
|
self->display_name = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_ACC_USER:
|
|
g_free (self->user);
|
|
self->user = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_ACC_PASSWORD:
|
|
g_free (self->password);
|
|
self->password = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_ACC_PORT:
|
|
self->port = g_value_get_int (value);
|
|
break;
|
|
|
|
case PROP_ACC_PROTOCOL:
|
|
g_free (self->transport_protocol);
|
|
self->transport_protocol = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_ACC_AUTO_CONNECT:
|
|
self->auto_connect = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_ACC_DIRECT:
|
|
self->use_direct_connection = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_SIP_CONTEXT:
|
|
self->ctx = g_value_get_pointer (value);
|
|
break;
|
|
|
|
case PROP_ACC_LOCAL_PORT:
|
|
if (g_value_get_int (value) > 0 && g_value_get_int (value) < 1025) {
|
|
g_warning ("Tried setting a privileged port as the local port to bind to: %d\n"
|
|
"Continue using old 'local-port' value: %d (using 0 let's the OS decide)",
|
|
g_value_get_int (value), self->local_port);
|
|
return;
|
|
}
|
|
self->local_port = g_value_get_int (value);
|
|
break;
|
|
|
|
case PROP_CAN_TEL:
|
|
self->can_tel = g_value_get_boolean (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
CallsSipOrigin *self = CALLS_SIP_ORIGIN (object);
|
|
g_autofree char *name = NULL;
|
|
|
|
switch (property_id) {
|
|
case PROP_NAME:
|
|
g_value_set_string (value, self->name);
|
|
break;
|
|
case PROP_ACC_HOST:
|
|
g_value_set_string (value, self->host);
|
|
break;
|
|
|
|
case PROP_ACC_DISPLAY_NAME:
|
|
g_value_set_string (value, self->display_name);
|
|
break;
|
|
|
|
case PROP_ACC_USER:
|
|
g_value_set_string (value, self->user);
|
|
break;
|
|
|
|
case PROP_ACC_PASSWORD:
|
|
g_value_set_string (value, self->password);
|
|
break;
|
|
|
|
case PROP_ACC_PORT:
|
|
g_value_set_int (value, self->port);
|
|
break;
|
|
|
|
case PROP_ACC_PROTOCOL:
|
|
g_value_set_string (value, self->transport_protocol);
|
|
break;
|
|
|
|
case PROP_ACC_AUTO_CONNECT:
|
|
g_value_set_boolean (value, self->auto_connect);
|
|
break;
|
|
|
|
case PROP_ACC_DIRECT:
|
|
g_value_set_boolean (value, self->direct_mode);
|
|
break;
|
|
|
|
case PROP_ACC_LOCAL_PORT:
|
|
g_value_set_int (value, self->local_port);
|
|
break;
|
|
|
|
case PROP_CALLS:
|
|
g_value_set_pointer (value, g_list_copy (self->calls));
|
|
break;
|
|
|
|
case PROP_ACC_STATE:
|
|
g_value_set_enum (value, self->state);
|
|
break;
|
|
|
|
case PROP_ACC_ADDRESS:
|
|
g_value_set_string (value, get_address (CALLS_ACCOUNT (self)));
|
|
break;
|
|
|
|
case PROP_COUNTRY_CODE:
|
|
g_value_set_string (value, NULL);
|
|
break;
|
|
|
|
case PROP_CAN_TEL:
|
|
g_value_set_boolean (value, self->can_tel);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_constructed (GObject *object)
|
|
{
|
|
CallsSipOrigin *self = CALLS_SIP_ORIGIN (object);
|
|
g_autoptr (GError) error = NULL;
|
|
int major = 0;
|
|
int minor = 0;
|
|
int patch = 0;
|
|
|
|
if (sscanf (SOFIA_SIP_VERSION, "%d.%d.%d", &major, &minor, &patch) == 3) {
|
|
if (major > 2 || (major == 1 && minor >= 13)) {
|
|
/* Sofia 1.13 does no longer include the contact header by default, see #304 */
|
|
set_contact_header = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!init_sip_account (self, &error)) {
|
|
g_warning ("Error initializing the SIP account: %s", error->message);
|
|
}
|
|
|
|
update_name (self);
|
|
|
|
self->media_manager = calls_sip_media_manager_default ();
|
|
|
|
G_OBJECT_CLASS (calls_sip_origin_parent_class)->constructed (object);
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_dispose (GObject *object)
|
|
{
|
|
CallsSipOrigin *self = CALLS_SIP_ORIGIN (object);
|
|
|
|
g_clear_pointer (&self->own_ip, g_free);
|
|
|
|
if (!self->use_direct_connection && self->state == CALLS_ACCOUNT_ONLINE)
|
|
go_online (CALLS_ACCOUNT (self), FALSE);
|
|
|
|
deinit_sip_account (self);
|
|
|
|
G_OBJECT_CLASS (calls_sip_origin_parent_class)->dispose (object);
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_finalize (GObject *object)
|
|
{
|
|
CallsSipOrigin *self = CALLS_SIP_ORIGIN (object);
|
|
|
|
g_hash_table_destroy (self->call_handles);
|
|
|
|
G_OBJECT_CLASS (calls_sip_origin_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_class_init (CallsSipOriginClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->constructed = calls_sip_origin_constructed;
|
|
object_class->dispose = calls_sip_origin_dispose;
|
|
object_class->finalize = calls_sip_origin_finalize;
|
|
object_class->get_property = calls_sip_origin_get_property;
|
|
object_class->set_property = calls_sip_origin_set_property;
|
|
|
|
props[PROP_ACC_HOST] =
|
|
g_param_spec_string ("host",
|
|
"Host",
|
|
"The host to connect to",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_HOST, props[PROP_ACC_HOST]);
|
|
|
|
props[PROP_ACC_USER] =
|
|
g_param_spec_string ("user",
|
|
"User",
|
|
"The username",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_USER, props[PROP_ACC_USER]);
|
|
|
|
props[PROP_ACC_PASSWORD] =
|
|
g_param_spec_string ("password",
|
|
"Password",
|
|
"The password",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_PASSWORD, props[PROP_ACC_PASSWORD]);
|
|
|
|
props[PROP_ACC_DISPLAY_NAME] =
|
|
g_param_spec_string ("display-name",
|
|
"Display name",
|
|
"The display name",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_DISPLAY_NAME, props[PROP_ACC_DISPLAY_NAME]);
|
|
|
|
props[PROP_ACC_PORT] =
|
|
g_param_spec_int ("port",
|
|
"Port",
|
|
"The port to connect to",
|
|
0, 65535, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_PORT, props[PROP_ACC_PORT]);
|
|
|
|
props[PROP_ACC_PROTOCOL] =
|
|
g_param_spec_string ("transport-protocol",
|
|
"Transport protocol",
|
|
"The transport protocol to use for the connection",
|
|
NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_PROTOCOL, props[PROP_ACC_PROTOCOL]);
|
|
|
|
props[PROP_ACC_AUTO_CONNECT] =
|
|
g_param_spec_boolean ("auto-connect",
|
|
"Auto connect",
|
|
"Whether to connect automatically",
|
|
TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_AUTO_CONNECT, props[PROP_ACC_AUTO_CONNECT]);
|
|
|
|
|
|
props[PROP_ACC_DIRECT] =
|
|
g_param_spec_boolean ("direct-mode",
|
|
"Direct mode",
|
|
"Whether to use a direct connection (no SIP server)",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_ACC_DIRECT, props[PROP_ACC_DIRECT]);
|
|
|
|
props[PROP_ACC_LOCAL_PORT] =
|
|
g_param_spec_int ("local-port",
|
|
"Local port",
|
|
"The local port to which the SIP stack binds to",
|
|
0, 65535, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_ACC_LOCAL_PORT, props[PROP_ACC_LOCAL_PORT]);
|
|
|
|
props[PROP_SIP_CONTEXT] =
|
|
g_param_spec_pointer ("sip-context",
|
|
"SIP context",
|
|
"The SIP context (sofia) used for our sip handles",
|
|
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_SIP_CONTEXT, props[PROP_SIP_CONTEXT]);
|
|
|
|
props[PROP_CAN_TEL] =
|
|
g_param_spec_boolean ("can-tel",
|
|
"Can telephone",
|
|
"Whether to this account can be used for PSTN telephony",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
g_object_class_install_property (object_class, PROP_CAN_TEL, props[PROP_CAN_TEL]);
|
|
|
|
g_object_class_override_property (object_class, PROP_ACC_STATE, "account-state");
|
|
props[PROP_ACC_STATE] = g_object_class_find_property (object_class, "account-state");
|
|
|
|
g_object_class_override_property (object_class, PROP_ACC_ADDRESS, "address");
|
|
props[PROP_ACC_ADDRESS] = g_object_class_find_property (object_class, "address");
|
|
|
|
#define IMPLEMENTS(ID, NAME) \
|
|
g_object_class_override_property (object_class, ID, NAME); \
|
|
props[ID] = g_object_class_find_property(object_class, NAME);
|
|
|
|
IMPLEMENTS (PROP_NAME, "name");
|
|
IMPLEMENTS (PROP_CALLS, "calls");
|
|
IMPLEMENTS (PROP_COUNTRY_CODE, "country-code");
|
|
|
|
#undef IMPLEMENTS
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_message_source_interface_init (CallsOriginInterface *iface)
|
|
{
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_origin_interface_init (CallsOriginInterface *iface)
|
|
{
|
|
iface->dial = dial;
|
|
iface->supports_protocol = supports_protocol;
|
|
}
|
|
|
|
static void
|
|
calls_sip_origin_accounts_interface_init (CallsAccountInterface *iface)
|
|
{
|
|
iface->go_online = go_online;
|
|
iface->get_address = get_address;
|
|
}
|
|
|
|
|
|
static void
|
|
calls_sip_origin_init (CallsSipOrigin *self)
|
|
{
|
|
const char *sip_test_env = g_getenv ("CALLS_SIP_TEST");
|
|
|
|
if (!sip_test_env || sip_test_env[0] == '\0') {
|
|
CallsNetworkWatch *nw = calls_network_watch_get_default ();
|
|
if (nw)
|
|
g_signal_connect_swapped (calls_network_watch_get_default (), "network-changed",
|
|
G_CALLBACK (recreate_sip), self);
|
|
else
|
|
g_warning ("Network watch unavailable. Unable to detect network changes.");
|
|
}
|
|
|
|
self->call_handles = g_hash_table_new (NULL, NULL);
|
|
|
|
}
|
|
|
|
void
|
|
calls_sip_origin_set_credentials (CallsSipOrigin *self,
|
|
const char *host,
|
|
const char *user,
|
|
const char *password,
|
|
const char *display_name,
|
|
const char *transport_protocol,
|
|
gint port,
|
|
gboolean can_tel,
|
|
gboolean auto_connect)
|
|
{
|
|
g_return_if_fail (CALLS_IS_SIP_ORIGIN (self));
|
|
|
|
if (self->direct_mode) {
|
|
g_warning ("Not allowed to update credentials when using direct mode");
|
|
return;
|
|
}
|
|
|
|
g_return_if_fail (host);
|
|
g_return_if_fail (user);
|
|
g_return_if_fail (password);
|
|
|
|
if (transport_protocol)
|
|
g_return_if_fail (protocol_is_valid (transport_protocol));
|
|
|
|
g_free (self->host);
|
|
self->host = g_strdup (host);
|
|
|
|
g_free (self->user);
|
|
self->user = g_strdup (user);
|
|
|
|
g_free (self->password);
|
|
self->password = g_strdup (password);
|
|
|
|
g_clear_pointer (&self->display_name, g_free);
|
|
if (display_name)
|
|
self->display_name = g_strdup (display_name);
|
|
|
|
g_free (self->transport_protocol);
|
|
if (transport_protocol)
|
|
self->transport_protocol = g_strdup (transport_protocol);
|
|
else
|
|
self->transport_protocol = g_strdup ("UDP");
|
|
|
|
self->port = port;
|
|
|
|
self->can_tel = can_tel;
|
|
|
|
recreate_sip (self);
|
|
}
|