Move provider plugins into a dedicated directory
Since we will introduce another type of plugin for the policy engine we want to have each plugin type in separate directories. We also have to adjust: - plugin search directories - po file location - update paths for calls-doc target
This commit is contained in:
committed by
Guido Günther
parent
8c6ece6a87
commit
86a8f3ae22
512
plugins/provider/sip/calls-sdp-crypto-context.c
Normal file
512
plugins/provider/sip/calls-sdp-crypto-context.c
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-sdp-crypto-context.h"
|
||||
#include "calls-sip-enums.h"
|
||||
|
||||
#include <sofia-sip/sdp.h>
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_STATE,
|
||||
PROP_LAST_PROP
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
struct _CallsSdpCryptoContext {
|
||||
GObject parent_instance;
|
||||
|
||||
GList *local_crypto_attributes;
|
||||
GList *remote_crypto_attributes;
|
||||
|
||||
CallsCryptoContextState state;
|
||||
int negotiated_tag;
|
||||
};
|
||||
|
||||
#if GLIB_CHECK_VERSION (2, 70, 0)
|
||||
G_DEFINE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT)
|
||||
#else
|
||||
G_DEFINE_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT)
|
||||
#endif
|
||||
|
||||
|
||||
static GStrv
|
||||
get_all_crypto_attributes_strv (sdp_media_t *media)
|
||||
{
|
||||
#if GLIB_CHECK_VERSION (2, 68, 0)
|
||||
g_autoptr (GStrvBuilder) builder = NULL;
|
||||
|
||||
g_assert (media);
|
||||
|
||||
builder = g_strv_builder_new ();
|
||||
|
||||
for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) {
|
||||
g_autofree char *crypto_str = NULL;
|
||||
|
||||
if (g_strcmp0 (attr->a_name, "crypto") != 0)
|
||||
continue;
|
||||
|
||||
crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL);
|
||||
g_strv_builder_add (builder, crypto_str);
|
||||
}
|
||||
|
||||
return g_strv_builder_end (builder);
|
||||
#else
|
||||
/* implement a poor mans GStrv */
|
||||
g_autofree char *attribute_string = NULL;
|
||||
|
||||
g_assert (media);
|
||||
|
||||
for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) {
|
||||
g_autofree char *crypto_str = NULL;
|
||||
|
||||
if (g_strcmp0 (attr->a_name, "crypto") != 0)
|
||||
continue;
|
||||
|
||||
crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL);
|
||||
if (!attribute_string) {
|
||||
attribute_string = g_strdup (crypto_str);
|
||||
} else {
|
||||
g_autofree char *tmp = attribute_string;
|
||||
attribute_string = g_strconcat (attribute_string, "\n", crypto_str, NULL);
|
||||
}
|
||||
}
|
||||
return g_strsplit (attribute_string, "\n", -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
crypto_attribute_is_supported (CallsSdpCryptoContext *self,
|
||||
calls_srtp_crypto_attribute *attr)
|
||||
{
|
||||
g_assert (attr);
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_UNKNOWN)
|
||||
return FALSE;
|
||||
|
||||
/* TODO setup a policy mechanism, for now this is hardcoded */
|
||||
if (attr->unencrypted_srtp ||
|
||||
attr->unauthenticated_srtp ||
|
||||
attr->unencrypted_srtcp)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static calls_srtp_crypto_attribute *
|
||||
get_crypto_attribute_by_tag (GList *attributes,
|
||||
guint tag)
|
||||
{
|
||||
|
||||
g_assert (attributes);
|
||||
g_assert (tag > 0);
|
||||
|
||||
for (GList *node = attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
|
||||
if (attr->tag == tag)
|
||||
return attr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_state (CallsSdpCryptoContext *self,
|
||||
CallsCryptoContextState state)
|
||||
{
|
||||
g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self));
|
||||
|
||||
if (self->state == state)
|
||||
return;
|
||||
|
||||
self->state = state;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]);
|
||||
}
|
||||
|
||||
|
||||
/* Returns if %TRUE if the state was updated, %FALSE otherwise */
|
||||
static gboolean
|
||||
update_state (CallsSdpCryptoContext *self)
|
||||
{
|
||||
GList *tags_local = NULL;
|
||||
GList *tags_remote = NULL;
|
||||
gint negotiated_tag = -1;
|
||||
calls_srtp_crypto_attribute *local_crypto;
|
||||
calls_srtp_crypto_attribute *remote_crypto;
|
||||
|
||||
|
||||
g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self));
|
||||
|
||||
/* Cannot update final states */
|
||||
if (self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED ||
|
||||
self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
|
||||
return FALSE;
|
||||
|
||||
if (self->state == CALLS_CRYPTO_CONTEXT_STATE_INIT) {
|
||||
if (self->local_crypto_attributes) {
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL);
|
||||
return TRUE;
|
||||
}
|
||||
if (self->remote_crypto_attributes) {
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (GList *node = self->local_crypto_attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
tags_local = g_list_append (tags_local, GUINT_TO_POINTER (attr->tag));
|
||||
}
|
||||
|
||||
for (GList *node = self->remote_crypto_attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
tags_remote = g_list_append (tags_remote, GUINT_TO_POINTER (attr->tag));
|
||||
}
|
||||
|
||||
if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL) {
|
||||
for (GList *node = tags_local; node; node = node->next) {
|
||||
if (g_list_find (tags_remote, node->data)) {
|
||||
negotiated_tag = GPOINTER_TO_UINT (node->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) {
|
||||
for (GList *node = tags_remote; node; node = node->next) {
|
||||
if (g_list_find (tags_local, node->data)) {
|
||||
negotiated_tag = GPOINTER_TO_UINT (node->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
g_list_free (tags_local);
|
||||
g_list_free (tags_remote);
|
||||
|
||||
if (negotiated_tag == -1) {
|
||||
self->state = CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
local_crypto = get_crypto_attribute_by_tag (self->local_crypto_attributes,
|
||||
negotiated_tag);
|
||||
remote_crypto = get_crypto_attribute_by_tag (self->remote_crypto_attributes,
|
||||
negotiated_tag);
|
||||
|
||||
if (local_crypto->crypto_suite != remote_crypto->crypto_suite) {
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* TODO check mandatory parameters and policy constrains */
|
||||
|
||||
self->negotiated_tag = negotiated_tag;
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_STATE:
|
||||
g_value_set_enum (value, self->state);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_dispose (GObject *object)
|
||||
{
|
||||
CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object);
|
||||
|
||||
g_clear_list (&self->local_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free);
|
||||
g_clear_list (&self->remote_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free);
|
||||
|
||||
G_OBJECT_CLASS (calls_sdp_crypto_context_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_class_init (CallsSdpCryptoContextClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = calls_sdp_crypto_context_dispose;
|
||||
object_class->get_property = calls_sdp_crypto_context_get_property;
|
||||
|
||||
props[PROP_STATE] =
|
||||
g_param_spec_enum ("state",
|
||||
"State",
|
||||
"State of the crypto context",
|
||||
CALLS_TYPE_CRYPTO_CONTEXT_STATE,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_INIT,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_init (CallsSdpCryptoContext *self)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CallsSdpCryptoContext *
|
||||
calls_sdp_crypto_context_new (void)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_SDP_CRYPTO_CONTEXT, NULL);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_set_local_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media)
|
||||
{
|
||||
g_auto (GStrv) crypto_strv = NULL;
|
||||
guint n_crypto_attr;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
g_return_val_if_fail (media, FALSE);
|
||||
|
||||
if (self->local_crypto_attributes) {
|
||||
g_warning ("Local crypto attributes already set");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
crypto_strv = get_all_crypto_attributes_strv (media);
|
||||
n_crypto_attr = g_strv_length (crypto_strv);
|
||||
|
||||
if (n_crypto_attr == 0) {
|
||||
g_warning ("No crypto attributes found in given SDP media");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < n_crypto_attr; i++) {
|
||||
g_autoptr (GError) error = NULL;
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error);
|
||||
if (!attr) {
|
||||
g_warning ("Failed parsing crypto attribute '%s': %s",
|
||||
crypto_strv[i], error->message);
|
||||
continue;
|
||||
}
|
||||
self->local_crypto_attributes =
|
||||
g_list_append (self->local_crypto_attributes, attr);
|
||||
}
|
||||
|
||||
if (!self->local_crypto_attributes) {
|
||||
g_warning ("Could not parse a single crypto attribute, aborting");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_set_remote_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media)
|
||||
{
|
||||
g_auto (GStrv) crypto_strv = NULL;
|
||||
|
||||
guint n_crypto_attr;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
g_return_val_if_fail (media, FALSE);
|
||||
|
||||
if (self->remote_crypto_attributes) {
|
||||
g_warning ("Remote crypto attributes already set");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
crypto_strv = get_all_crypto_attributes_strv (media);
|
||||
n_crypto_attr = g_strv_length (crypto_strv);
|
||||
|
||||
if (n_crypto_attr == 0) {
|
||||
g_warning ("No crypto attributes found in given SDP media");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < n_crypto_attr; i++) {
|
||||
g_autoptr (GError) error = NULL;
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error);
|
||||
if (!attr) {
|
||||
g_warning ("Failed parsing crypto attribute '%s': %s",
|
||||
crypto_strv[i], error->message);
|
||||
continue;
|
||||
}
|
||||
self->remote_crypto_attributes =
|
||||
g_list_append (self->remote_crypto_attributes, attr);
|
||||
}
|
||||
|
||||
if (!self->remote_crypto_attributes) {
|
||||
g_warning ("Could not parse a single crypto attribute, aborting");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_sdp_crypto_context_get_local_crypto (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
return get_crypto_attribute_by_tag (self->local_crypto_attributes,
|
||||
self->negotiated_tag);
|
||||
}
|
||||
|
||||
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_sdp_crypto_context_get_remote_crypto (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
return get_crypto_attribute_by_tag (self->remote_crypto_attributes,
|
||||
self->negotiated_tag);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_generate_offer (CallsSdpCryptoContext *self)
|
||||
{
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_INIT) {
|
||||
g_warning ("Cannot generate offer. Need INIT state, but found %d",
|
||||
self->state);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_assert (!self->local_crypto_attributes);
|
||||
|
||||
attr = calls_srtp_crypto_attribute_new (1);
|
||||
attr->tag = 1;
|
||||
attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_80;
|
||||
calls_srtp_crypto_attribute_init_keys (attr);
|
||||
|
||||
self->local_crypto_attributes = g_list_append (NULL, attr);
|
||||
|
||||
attr = calls_srtp_crypto_attribute_new (1);
|
||||
attr->tag = 2;
|
||||
attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_32;
|
||||
calls_srtp_crypto_attribute_init_keys (attr);
|
||||
|
||||
self->local_crypto_attributes = g_list_append (self->local_crypto_attributes, attr);
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_generate_answer (CallsSdpCryptoContext *self)
|
||||
{
|
||||
calls_srtp_crypto_attribute *attr = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) {
|
||||
g_warning ("Cannot generate answer. Need OFFER_REMOTE state, but found %d",
|
||||
self->state);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (GList *node = self->remote_crypto_attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr_offer = node->data;
|
||||
|
||||
if (crypto_attribute_is_supported (self, attr_offer)) {
|
||||
attr = calls_srtp_crypto_attribute_new (1);
|
||||
attr->crypto_suite = attr_offer->crypto_suite;
|
||||
attr->tag = attr_offer->tag;
|
||||
calls_srtp_crypto_attribute_init_keys (attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!attr)
|
||||
return FALSE;
|
||||
|
||||
self->local_crypto_attributes = g_list_append (NULL, attr);
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_get_is_negotiated (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
|
||||
return self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
CallsCryptoContextState
|
||||
calls_sdp_crypto_context_get_state (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), CALLS_CRYPTO_CONTEXT_STATE_INIT);
|
||||
|
||||
return self->state;
|
||||
}
|
||||
|
||||
|
||||
GList *
|
||||
calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self,
|
||||
gboolean remote)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
|
||||
|
||||
return g_list_copy (remote ? self->remote_crypto_attributes : self->local_crypto_attributes);
|
||||
}
|
||||
66
plugins/provider/sip/calls-sdp-crypto-context.h
Normal file
66
plugins/provider/sip/calls-sdp-crypto-context.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-srtp-utils.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include <sofia-sip/sdp.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/** XXX media line with cryptographic key parameters or session parameters that we
|
||||
* do not support MUST be rejected (https://datatracker.ietf.org/doc/html/rfc4568#section-7.1.2)
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
CALLS_CRYPTO_CONTEXT_STATE_INIT = 0,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS
|
||||
} CallsCryptoContextState;
|
||||
|
||||
|
||||
#define CALLS_TYPE_SDP_CRYPTO_CONTEXT (calls_sdp_crypto_context_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, CALLS, SDP_CRYPTO_CONTEXT, GObject);
|
||||
|
||||
CallsSdpCryptoContext *calls_sdp_crypto_context_new (void);
|
||||
gboolean calls_sdp_crypto_context_set_local_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media);
|
||||
gboolean calls_sdp_crypto_context_set_remote_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media);
|
||||
calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_local_crypto (CallsSdpCryptoContext *self);
|
||||
calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_remote_crypto (CallsSdpCryptoContext *self);
|
||||
gboolean calls_sdp_crypto_context_generate_offer (CallsSdpCryptoContext *self);
|
||||
gboolean calls_sdp_crypto_context_generate_answer (CallsSdpCryptoContext *self);
|
||||
gboolean calls_sdp_crypto_context_get_is_negotiated (CallsSdpCryptoContext *self);
|
||||
CallsCryptoContextState calls_sdp_crypto_context_get_state (CallsSdpCryptoContext *self);
|
||||
GList *calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self,
|
||||
gboolean remote);
|
||||
|
||||
G_END_DECLS
|
||||
735
plugins/provider/sip/calls-sip-account-widget.c
Normal file
735
plugins/provider/sip/calls-sip-account-widget.c
Normal file
@@ -0,0 +1,735 @@
|
||||
/*
|
||||
* 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 "CallsSipAccountWidget"
|
||||
|
||||
#include "calls-settings.h"
|
||||
#include "calls-sip-account-widget.h"
|
||||
#include "calls-sip-provider.h"
|
||||
#include "calls-sip-origin.h"
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
/**
|
||||
* Section:calls-sip-account-widget
|
||||
* short_description: A #GtkWidget to edit or add SIP accounts
|
||||
* @Title: CallsSipAccountWidget
|
||||
*
|
||||
* This #GtkWidget allows the user to add a new or edit an existing SIP account.
|
||||
*/
|
||||
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PROVIDER,
|
||||
PROP_ORIGIN,
|
||||
PROP_LAST_PROP
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
|
||||
struct _CallsSipAccountWidget {
|
||||
GtkBox parent;
|
||||
|
||||
/* Header bar */
|
||||
GtkWidget *header_add;
|
||||
GtkSpinner *spinner_add;
|
||||
GtkWidget *header_edit;
|
||||
GtkSpinner *spinner_edit;
|
||||
GtkWidget *login_btn;
|
||||
GtkWidget *apply_btn;
|
||||
GtkWidget *delete_btn;
|
||||
|
||||
/* widgets for editing account credentials */
|
||||
GtkEntry *host;
|
||||
GtkEntry *display_name;
|
||||
GtkEntry *user;
|
||||
GtkEntry *password;
|
||||
GtkEntry *port;
|
||||
char *last_port;
|
||||
HdyComboRow *protocol;
|
||||
GListStore *protocols_store; /* bound model for protocol HdyComboRow */
|
||||
HdyComboRow *media_encryption;
|
||||
GListStore *media_encryption_store;
|
||||
GtkSwitch *tel_switch;
|
||||
GtkSwitch *auto_connect_switch;
|
||||
|
||||
|
||||
/* properties */
|
||||
CallsSipProvider *provider;
|
||||
CallsSipOrigin *origin; /* nullable to add a new account */
|
||||
|
||||
/* misc */
|
||||
CallsSettings *settings;
|
||||
gboolean connecting;
|
||||
gboolean port_self_change;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (CallsSipAccountWidget, calls_sip_account_widget, GTK_TYPE_BOX)
|
||||
|
||||
|
||||
static gboolean
|
||||
is_form_valid (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
/* TODO perform some sanity checks */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
is_form_filled (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
return
|
||||
g_strcmp0 (gtk_entry_get_text (self->host), "") != 0 &&
|
||||
g_strcmp0 (gtk_entry_get_text (self->user), "") != 0 &&
|
||||
g_strcmp0 (gtk_entry_get_text (self->password), "") != 0 &&
|
||||
g_strcmp0 (gtk_entry_get_text (self->port), "") != 0;
|
||||
}
|
||||
|
||||
static const char *
|
||||
get_selected_protocol (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_autoptr (HdyValueObject) obj = NULL;
|
||||
const char *protocol = NULL;
|
||||
gint i;
|
||||
|
||||
if ((i = hdy_combo_row_get_selected_index (self->protocol)) != -1) {
|
||||
obj = g_list_model_get_item (G_LIST_MODEL (self->protocols_store), i);
|
||||
protocol = hdy_value_object_get_string (obj);
|
||||
}
|
||||
return protocol;
|
||||
}
|
||||
|
||||
|
||||
static SipMediaEncryption
|
||||
get_selected_media_encryption (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_autoptr (HdyValueObject) obj = NULL;
|
||||
SipMediaEncryption media_encryption = SIP_MEDIA_ENCRYPTION_NONE;
|
||||
gint i;
|
||||
|
||||
if ((i = hdy_combo_row_get_selected_index (self->media_encryption)) != -1) {
|
||||
obj = g_list_model_get_item (G_LIST_MODEL (self->media_encryption_store), i);
|
||||
media_encryption = (SipMediaEncryption) GPOINTER_TO_INT (g_object_get_data (G_OBJECT (obj), "value"));
|
||||
}
|
||||
|
||||
|
||||
return media_encryption;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
update_media_encryption (CallsSipAccountWidget *self)
|
||||
{
|
||||
gboolean transport_is_tls;
|
||||
gboolean sdes_always_allowed;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
transport_is_tls = g_strcmp0 (get_selected_protocol (self), "TLS") == 0;
|
||||
sdes_always_allowed = calls_settings_get_always_allow_sdes (self->settings);
|
||||
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self->media_encryption),
|
||||
transport_is_tls | sdes_always_allowed);
|
||||
|
||||
if (!transport_is_tls && !sdes_always_allowed)
|
||||
hdy_combo_row_set_selected_index (self->media_encryption, 0);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_user_changed (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
gtk_widget_set_sensitive (self->login_btn,
|
||||
is_form_filled (self) &&
|
||||
is_form_valid (self));
|
||||
|
||||
gtk_widget_set_sensitive (self->apply_btn,
|
||||
is_form_filled (self) &&
|
||||
is_form_valid (self));
|
||||
|
||||
update_media_encryption (self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_password_visibility (CallsSipAccountWidget *self, gboolean visible)
|
||||
{
|
||||
const char *icon_name;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_assert (GTK_IS_ENTRY (self->password));
|
||||
|
||||
icon_name = visible ?
|
||||
"view-conceal-symbolic" :
|
||||
"view-reveal-symbolic";
|
||||
|
||||
gtk_entry_set_visibility (self->password, visible);
|
||||
gtk_entry_set_icon_from_icon_name (self->password, GTK_ENTRY_ICON_SECONDARY,
|
||||
icon_name);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_password_visibility_changed (CallsSipAccountWidget *self,
|
||||
GtkEntryIconPosition icon_pos,
|
||||
GdkEvent *event,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
gboolean visible;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_assert (GTK_IS_ENTRY (entry));
|
||||
g_assert (icon_pos == GTK_ENTRY_ICON_SECONDARY);
|
||||
|
||||
visible = !gtk_entry_get_visibility (entry);
|
||||
set_password_visibility (self, visible);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop "insert-text" signal emission if any undesired port
|
||||
* value occurs
|
||||
*/
|
||||
static void
|
||||
on_port_entry_insert_text (CallsSipAccountWidget *self,
|
||||
char *new_text,
|
||||
int new_text_length,
|
||||
gpointer position,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
size_t digit_end, len;
|
||||
int *pos;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_assert (GTK_IS_ENTRY (entry));
|
||||
|
||||
if (!new_text || !*new_text || self->port_self_change)
|
||||
return;
|
||||
|
||||
pos = (int *) position;
|
||||
g_object_set_data (G_OBJECT (entry), "old-pos", GINT_TO_POINTER (*pos));
|
||||
|
||||
if (new_text_length == -1)
|
||||
len = strlen (new_text);
|
||||
else
|
||||
len = new_text_length;
|
||||
|
||||
digit_end = strspn (new_text, "1234567890");
|
||||
|
||||
/* If user inserted something other than a digit,
|
||||
* stop inserting the text and warn the user.
|
||||
*/
|
||||
if (digit_end != len) {
|
||||
g_signal_stop_emission_by_name (entry, "insert-text");
|
||||
gtk_widget_error_bell (GTK_WIDGET (entry));
|
||||
} else {
|
||||
g_free (self->last_port);
|
||||
self->last_port = g_strdup (gtk_entry_get_text (entry));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
update_port_cursor_position (GtkEntry *entry)
|
||||
{
|
||||
int pos;
|
||||
|
||||
pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (entry), "old-pos"));
|
||||
gtk_editable_set_position (GTK_EDITABLE (entry), pos);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
get_port (CallsSipAccountWidget *self)
|
||||
{
|
||||
const char *text;
|
||||
int port = 0;
|
||||
|
||||
text = gtk_entry_get_text (self->port);
|
||||
port = (int) g_ascii_strtod (text, NULL);
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_port_entry_after_insert_text (CallsSipAccountWidget *self,
|
||||
char *new_text,
|
||||
int new_text_length,
|
||||
gpointer position,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
int port = get_port (self);
|
||||
|
||||
/* Reset to the old value if new port number is invalid */
|
||||
if ((port < 0 || port > 65535) && self->last_port) {
|
||||
self->port_self_change = TRUE;
|
||||
gtk_entry_set_text (entry, self->last_port);
|
||||
g_idle_add (G_SOURCE_FUNC (update_port_cursor_position), entry);
|
||||
gtk_widget_error_bell (GTK_WIDGET (entry));
|
||||
self->port_self_change = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_header (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
if (self->origin) {
|
||||
gtk_widget_show (self->header_edit);
|
||||
gtk_widget_hide (self->header_add);
|
||||
|
||||
} else {
|
||||
gtk_widget_show (self->header_add);
|
||||
gtk_widget_hide (self->header_edit);
|
||||
}
|
||||
|
||||
if (self->connecting) {
|
||||
gtk_spinner_start (self->spinner_add);
|
||||
gtk_spinner_start (self->spinner_edit);
|
||||
} else {
|
||||
gtk_spinner_stop (self->spinner_add);
|
||||
gtk_spinner_stop (self->spinner_edit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
find_protocol (CallsSipAccountWidget *self,
|
||||
const char *protocol,
|
||||
guint *index)
|
||||
{
|
||||
guint len;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
len = g_list_model_get_n_items (G_LIST_MODEL (self->protocols_store));
|
||||
for (guint i = 0; i < len; i++) {
|
||||
g_autoptr (HdyValueObject) obj =
|
||||
g_list_model_get_item (G_LIST_MODEL (self->protocols_store), i);
|
||||
const char *prot = hdy_value_object_get_string (obj);
|
||||
|
||||
if (g_strcmp0 (protocol, prot) == 0) {
|
||||
if (index)
|
||||
*index = i;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
g_warning ("Could not find protocol '%s'", protocol);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
find_media_encryption (CallsSipAccountWidget *self,
|
||||
const SipMediaEncryption encryption,
|
||||
guint *index)
|
||||
{
|
||||
guint len;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
len = g_list_model_get_n_items (G_LIST_MODEL (self->media_encryption_store));
|
||||
|
||||
for (guint i = 0; i < len; i++) {
|
||||
g_autoptr (HdyValueObject) obj =
|
||||
g_list_model_get_item (G_LIST_MODEL (self->media_encryption_store), i);
|
||||
SipMediaEncryption obj_enc =
|
||||
(SipMediaEncryption) GPOINTER_TO_INT (g_object_get_data (G_OBJECT (obj), "value"));
|
||||
|
||||
if (obj_enc == encryption) {
|
||||
if (index)
|
||||
*index = i;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
g_warning ("Could not find encryption mode %d", encryption);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
clear_form (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
gtk_entry_set_text (self->host, "");
|
||||
gtk_entry_set_text (self->display_name, "");
|
||||
gtk_entry_set_text (self->user, "");
|
||||
gtk_entry_set_text (self->password, "");
|
||||
gtk_entry_set_text (self->port, "0");
|
||||
hdy_combo_row_set_selected_index (self->protocol, 0);
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self->media_encryption), FALSE);
|
||||
hdy_combo_row_set_selected_index (self->media_encryption, 0);
|
||||
gtk_switch_set_state (self->tel_switch, FALSE);
|
||||
gtk_switch_set_state (self->auto_connect_switch, TRUE);
|
||||
|
||||
self->origin = NULL;
|
||||
|
||||
update_header (self);
|
||||
|
||||
if (gtk_widget_get_can_focus (GTK_WIDGET (self->host)))
|
||||
gtk_widget_grab_focus (GTK_WIDGET (self->host));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
edit_form (CallsSipAccountWidget *self,
|
||||
CallsSipOrigin *origin)
|
||||
{
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *display_name = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
g_autofree char *password = NULL;
|
||||
g_autofree char *port_str = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
gint port;
|
||||
SipMediaEncryption encryption;
|
||||
guint encryption_index;
|
||||
guint protocol_index;
|
||||
gboolean can_tel;
|
||||
gboolean auto_connect;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
if (!origin) {
|
||||
clear_form (self);
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
||||
|
||||
self->origin = origin;
|
||||
|
||||
g_object_get (origin,
|
||||
"host", &host,
|
||||
"display-name", &display_name,
|
||||
"user", &user,
|
||||
"password", &password,
|
||||
"port", &port,
|
||||
"transport-protocol", &protocol,
|
||||
"media-encryption", &encryption,
|
||||
"can-tel", &can_tel,
|
||||
"auto-connect", &auto_connect,
|
||||
NULL);
|
||||
|
||||
port_str = g_strdup_printf ("%d", port);
|
||||
|
||||
/* The following should always succeed,
|
||||
TODO inform user in the error case
|
||||
related issue #275 https://source.puri.sm/Librem5/calls/-/issues/275
|
||||
*/
|
||||
if (!find_protocol (self, protocol, &protocol_index))
|
||||
protocol_index = 0;
|
||||
|
||||
if (!find_media_encryption (self, encryption, &encryption_index))
|
||||
encryption_index = 0;
|
||||
|
||||
/* set UI elements */
|
||||
gtk_entry_set_text (self->host, host);
|
||||
gtk_entry_set_text (self->display_name, display_name ?: "");
|
||||
gtk_entry_set_text (self->user, user);
|
||||
gtk_entry_set_text (self->password, password);
|
||||
set_password_visibility (self, FALSE);
|
||||
gtk_entry_set_text (self->port, port_str);
|
||||
hdy_combo_row_set_selected_index (self->protocol, protocol_index);
|
||||
hdy_combo_row_set_selected_index (self->media_encryption, encryption_index);
|
||||
gtk_switch_set_state (self->tel_switch, can_tel);
|
||||
gtk_switch_set_state (self->auto_connect_switch, auto_connect);
|
||||
|
||||
gtk_widget_set_sensitive (self->apply_btn, FALSE);
|
||||
|
||||
update_header (self);
|
||||
|
||||
if (gtk_widget_get_can_focus (GTK_WIDGET (self->host)))
|
||||
gtk_widget_grab_focus (GTK_WIDGET (self->host));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_login_clicked (CallsSipAccountWidget *self)
|
||||
{
|
||||
CallsSipOrigin *origin;
|
||||
g_autofree char *id = g_uuid_string_random ();
|
||||
|
||||
g_debug ("Logging into newly created account");
|
||||
|
||||
origin = calls_sip_provider_add_origin (self->provider,
|
||||
id,
|
||||
gtk_entry_get_text (GTK_ENTRY (self->host)),
|
||||
gtk_entry_get_text (GTK_ENTRY (self->user)),
|
||||
gtk_entry_get_text (GTK_ENTRY (self->password)),
|
||||
gtk_entry_get_text (GTK_ENTRY (self->display_name)),
|
||||
get_selected_protocol (self),
|
||||
get_port (self),
|
||||
get_selected_media_encryption (self),
|
||||
TRUE);
|
||||
|
||||
self->origin = origin;
|
||||
update_header (self);
|
||||
g_signal_emit_by_name (self->provider, "widget-edit-done");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_delete_clicked (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_debug ("Deleting account");
|
||||
|
||||
calls_sip_provider_remove_origin (self->provider, self->origin);
|
||||
self->origin = NULL;
|
||||
|
||||
update_header (self);
|
||||
g_signal_emit_by_name (self->provider, "widget-edit-done");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_apply_clicked (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_debug ("Applying changes to the account");
|
||||
|
||||
calls_sip_origin_set_credentials (self->origin,
|
||||
gtk_entry_get_text (self->host),
|
||||
gtk_entry_get_text (self->user),
|
||||
gtk_entry_get_text (self->password),
|
||||
gtk_entry_get_text (self->display_name),
|
||||
get_selected_protocol (self),
|
||||
get_port (self),
|
||||
get_selected_media_encryption (self),
|
||||
gtk_switch_get_state (self->tel_switch),
|
||||
gtk_switch_get_state (self->auto_connect_switch));
|
||||
|
||||
update_header (self);
|
||||
calls_sip_provider_save_accounts_to_disk (self->provider);
|
||||
g_signal_emit_by_name (self->provider, "widget-edit-done");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipAccountWidget *self = CALLS_SIP_ACCOUNT_WIDGET (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_PROVIDER:
|
||||
self->provider = g_value_get_object (value);
|
||||
break;
|
||||
|
||||
case PROP_ORIGIN:
|
||||
calls_sip_account_widget_set_origin (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipAccountWidget *self = CALLS_SIP_ACCOUNT_WIDGET (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ORIGIN:
|
||||
g_value_set_object (value, calls_sip_account_widget_get_origin (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_dispose (GObject *object)
|
||||
{
|
||||
CallsSipAccountWidget *self = CALLS_SIP_ACCOUNT_WIDGET (object);
|
||||
|
||||
g_clear_pointer (&self->last_port, g_free);
|
||||
g_clear_object (&self->protocols_store);
|
||||
g_clear_object (&self->media_encryption_store);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_account_widget_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_class_init (CallsSipAccountWidgetClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->set_property = calls_sip_account_widget_set_property;
|
||||
object_class->get_property = calls_sip_account_widget_get_property;
|
||||
object_class->dispose = calls_sip_account_widget_dispose;
|
||||
|
||||
props[PROP_PROVIDER] =
|
||||
g_param_spec_object ("provider",
|
||||
"Provider",
|
||||
"The SIP provider",
|
||||
CALLS_TYPE_SIP_PROVIDER,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_ORIGIN] =
|
||||
g_param_spec_object ("origin",
|
||||
"Origin",
|
||||
"The origin to edit",
|
||||
CALLS_TYPE_SIP_ORIGIN,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Calls/ui/sip-account-widget.ui");
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, header_add);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, spinner_add);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, header_edit);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, spinner_edit);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, login_btn);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, apply_btn);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, host);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, display_name);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, user);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, password);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, port);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, protocol);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, media_encryption);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, tel_switch);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, auto_connect_switch);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_login_clicked);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_delete_clicked);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_apply_clicked);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_user_changed);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_password_visibility_changed);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_port_entry_insert_text);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_port_entry_after_insert_text);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_init (CallsSipAccountWidget *self)
|
||||
{
|
||||
HdyValueObject *obj;
|
||||
|
||||
self->settings = calls_settings_get_default ();
|
||||
|
||||
g_signal_connect_swapped (self->settings,
|
||||
"notify::always-allow-sdes",
|
||||
G_CALLBACK (update_media_encryption),
|
||||
self);
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
self->media_encryption_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
|
||||
|
||||
obj = hdy_value_object_new_string (_("No encryption"));
|
||||
g_object_set_data (G_OBJECT (obj),
|
||||
"value", GINT_TO_POINTER (SIP_MEDIA_ENCRYPTION_NONE));
|
||||
g_list_store_insert (self->media_encryption_store, 0, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
/* TODO Optional encryption */
|
||||
obj = hdy_value_object_new_string (_("Force encryption"));
|
||||
g_object_set_data (G_OBJECT (obj),
|
||||
"value", GINT_TO_POINTER (SIP_MEDIA_ENCRYPTION_FORCED));
|
||||
g_list_store_insert (self->media_encryption_store, 1, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
hdy_combo_row_bind_name_model (self->media_encryption,
|
||||
G_LIST_MODEL (self->media_encryption_store),
|
||||
(HdyComboRowGetNameFunc) hdy_value_object_dup_string,
|
||||
NULL, NULL);
|
||||
|
||||
self->protocols_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
|
||||
|
||||
obj = hdy_value_object_new_string ("UDP");
|
||||
g_list_store_insert (self->protocols_store, 0, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
obj = hdy_value_object_new_string ("TCP");
|
||||
g_list_store_insert (self->protocols_store, 1, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
obj = hdy_value_object_new_string ("TLS");
|
||||
g_list_store_insert (self->protocols_store, 2, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
hdy_combo_row_bind_name_model (self->protocol,
|
||||
G_LIST_MODEL (self->protocols_store),
|
||||
(HdyComboRowGetNameFunc) hdy_value_object_dup_string,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
CallsSipAccountWidget *
|
||||
calls_sip_account_widget_new (CallsSipProvider *provider)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_PROVIDER (provider), NULL);
|
||||
|
||||
return g_object_new (CALLS_TYPE_SIP_ACCOUNT_WIDGET,
|
||||
"provider", provider,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
CallsSipOrigin *
|
||||
calls_sip_account_widget_get_origin (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_ACCOUNT_WIDGET (self), NULL);
|
||||
|
||||
return self->origin;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_sip_account_widget_set_origin (CallsSipAccountWidget *self,
|
||||
CallsSipOrigin *origin)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_return_if_fail (!origin || CALLS_IS_SIP_ORIGIN (origin));
|
||||
|
||||
edit_form (self, origin);
|
||||
}
|
||||
42
plugins/provider/sip/calls-sip-account-widget.h
Normal file
42
plugins/provider/sip/calls-sip-account-widget.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-sip-provider.h"
|
||||
|
||||
#include <handy.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_ACCOUNT_WIDGET (calls_sip_account_widget_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipAccountWidget, calls_sip_account_widget, CALLS, SIP_ACCOUNT_WIDGET, GtkBox)
|
||||
|
||||
CallsSipAccountWidget *calls_sip_account_widget_new (CallsSipProvider *provider);
|
||||
void calls_sip_account_widget_set_origin (CallsSipAccountWidget *self,
|
||||
CallsSipOrigin *origin);
|
||||
CallsSipOrigin *calls_sip_account_widget_get_origin (CallsSipAccountWidget *self);
|
||||
|
||||
G_END_DECLS
|
||||
457
plugins/provider/sip/calls-sip-call.c
Normal file
457
plugins/provider/sip/calls-sip-call.c
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* 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 "CallsSipCall"
|
||||
|
||||
|
||||
#include "calls-call.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-sdp-crypto-context.h"
|
||||
#include "calls-sip-call.h"
|
||||
#include "calls-sip-enums.h"
|
||||
#include "calls-sip-media-manager.h"
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "calls-sip-util.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include <sofia-sip/nua.h>
|
||||
|
||||
/**
|
||||
* SECTION:sip-call
|
||||
* @short_description: A #CallsCall for the SIP protocol
|
||||
* @Title: CallsSipCall
|
||||
*
|
||||
* #CallsSipCall derives from #CallsCall. Apart from allowing call control
|
||||
* like answering and hanging up it also coordinates with #CallsSipMediaManager
|
||||
* to prepare and control appropriate #CallsSipMediaPipeline objects.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_CALL_HANDLE,
|
||||
PROP_IP,
|
||||
PROP_PIPELINE,
|
||||
PROP_MEDIA_ENCRYPTION,
|
||||
PROP_LAST_PROP
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
struct _CallsSipCall {
|
||||
GObject parent_instance;
|
||||
|
||||
CallsSipMediaManager *manager;
|
||||
CallsSipMediaPipeline *pipeline;
|
||||
|
||||
char *ip;
|
||||
|
||||
guint rport_rtp;
|
||||
guint rport_rtcp;
|
||||
gchar *remote;
|
||||
|
||||
nua_handle_t *nh;
|
||||
GList *codecs;
|
||||
|
||||
CallsSdpCryptoContext *sdp_crypto_context;
|
||||
SipMediaEncryption media_encryption;
|
||||
};
|
||||
|
||||
static void calls_sip_call_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsSipCall, calls_sip_call, CALLS_TYPE_CALL,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_sip_call_message_source_interface_init))
|
||||
|
||||
static void
|
||||
calls_sip_call_answer (CallsCall *call)
|
||||
{
|
||||
CallsSipCall *self;
|
||||
g_autofree gchar *local_sdp = NULL;
|
||||
guint rtp_port, rtcp_port;
|
||||
g_autoptr (GList) local_crypto = NULL;
|
||||
gboolean got_crypto_offer;
|
||||
|
||||
g_assert (CALLS_IS_CALL (call));
|
||||
g_assert (CALLS_IS_SIP_CALL (call));
|
||||
|
||||
self = CALLS_SIP_CALL (call);
|
||||
|
||||
g_assert (self->nh);
|
||||
|
||||
if (calls_call_get_state (CALLS_CALL (self)) != CALLS_CALL_STATE_INCOMING) {
|
||||
g_warning ("Call must be in 'incoming' state in order to answer");
|
||||
return;
|
||||
}
|
||||
|
||||
rtp_port = calls_sip_media_pipeline_get_rtp_port (self->pipeline);
|
||||
rtcp_port = calls_sip_media_pipeline_get_rtcp_port (self->pipeline);
|
||||
|
||||
got_crypto_offer =
|
||||
calls_sdp_crypto_context_get_state (self->sdp_crypto_context) ==
|
||||
CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE;
|
||||
|
||||
if (got_crypto_offer) {
|
||||
if (self->media_encryption == SIP_MEDIA_ENCRYPTION_NONE) {
|
||||
g_warning ("Encryption disabled, but got offer. Call should have already been declined!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!calls_sdp_crypto_context_generate_answer (self->sdp_crypto_context)) {
|
||||
g_warning ("Could not generate answer for crypto key exchange. Aborting!");
|
||||
CALLS_EMIT_MESSAGE(self, _("Cryptographic key exchange unsuccessful"), GTK_MESSAGE_WARNING);
|
||||
/* XXX this should (probably) never be reached */
|
||||
nua_respond (self->nh, 488, "Not acceptable here", TAG_END ());
|
||||
return;
|
||||
}
|
||||
|
||||
local_crypto = calls_sdp_crypto_context_get_crypto_candidates (self->sdp_crypto_context, FALSE);
|
||||
} else {
|
||||
if (self->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
|
||||
g_warning ("Encryption forced, but got no offer. Call should have already been declined!");
|
||||
return;
|
||||
} else if (self->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
|
||||
g_debug ("Encryption optional, got no offer. Continuing unencrypted");
|
||||
}
|
||||
}
|
||||
|
||||
local_sdp = calls_sip_media_manager_get_capabilities (self->manager,
|
||||
self->ip,
|
||||
rtp_port,
|
||||
rtcp_port,
|
||||
local_crypto,
|
||||
self->codecs);
|
||||
|
||||
g_assert (local_sdp);
|
||||
g_debug ("Setting local SDP to string:\n%s", local_sdp);
|
||||
|
||||
nua_respond (self->nh, 200, NULL,
|
||||
SOATAG_USER_SDP_STR (local_sdp),
|
||||
SOATAG_AF (SOA_AF_IP4_IP6),
|
||||
TAG_END ());
|
||||
|
||||
calls_call_set_state (CALLS_CALL (self), CALLS_CALL_STATE_ACTIVE);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_hang_up (CallsCall *call)
|
||||
{
|
||||
CallsSipCall *self;
|
||||
|
||||
g_assert (CALLS_IS_CALL (call));
|
||||
g_assert (CALLS_IS_SIP_CALL (call));
|
||||
|
||||
self = CALLS_SIP_CALL (call);
|
||||
|
||||
switch (calls_call_get_state (call)) {
|
||||
case CALLS_CALL_STATE_DIALING:
|
||||
nua_cancel (self->nh, TAG_END ());
|
||||
g_debug ("Hanging up on outgoing ringing call");
|
||||
break;
|
||||
|
||||
case CALLS_CALL_STATE_ACTIVE:
|
||||
nua_bye (self->nh, TAG_END ());
|
||||
|
||||
g_debug ("Hanging up ongoing call");
|
||||
break;
|
||||
|
||||
case CALLS_CALL_STATE_INCOMING:
|
||||
nua_respond (self->nh, 480, NULL, TAG_END ());
|
||||
g_debug ("Hanging up incoming call");
|
||||
break;
|
||||
|
||||
case CALLS_CALL_STATE_DISCONNECTED:
|
||||
g_warning ("Tried hanging up already disconnected call");
|
||||
break;
|
||||
|
||||
default:
|
||||
g_warning ("Hanging up not possible in state %d",
|
||||
calls_call_get_state (call));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipCall *self = CALLS_SIP_CALL (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_CALL_HANDLE:
|
||||
self->nh = g_value_get_pointer (value);
|
||||
break;
|
||||
|
||||
case PROP_IP:
|
||||
g_free (self->ip);
|
||||
self->ip = g_value_dup_string (value);
|
||||
break;
|
||||
|
||||
case PROP_PIPELINE:
|
||||
self->pipeline = g_value_dup_object (value);
|
||||
break;
|
||||
|
||||
case PROP_MEDIA_ENCRYPTION:
|
||||
self->media_encryption = g_value_get_enum (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipCall *self = CALLS_SIP_CALL (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_CALL_HANDLE:
|
||||
g_value_set_pointer (value, self->nh);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_finalize (GObject *object)
|
||||
{
|
||||
CallsSipCall *self = CALLS_SIP_CALL (object);
|
||||
|
||||
calls_sip_media_pipeline_stop (self->pipeline);
|
||||
|
||||
g_clear_object (&self->pipeline);
|
||||
g_clear_pointer (&self->codecs, g_list_free);
|
||||
g_clear_pointer (&self->remote, g_free);
|
||||
g_clear_pointer (&self->ip, g_free);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_call_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_class_init (CallsSipCallClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsCallClass *call_class = CALLS_CALL_CLASS (klass);
|
||||
|
||||
object_class->get_property = calls_sip_call_get_property;
|
||||
object_class->set_property = calls_sip_call_set_property;
|
||||
object_class->finalize = calls_sip_call_finalize;
|
||||
|
||||
call_class->answer = calls_sip_call_answer;
|
||||
call_class->hang_up = calls_sip_call_hang_up;
|
||||
|
||||
props[PROP_CALL_HANDLE] =
|
||||
g_param_spec_pointer ("nua-handle",
|
||||
"NUA handle",
|
||||
"The used NUA handler",
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
||||
|
||||
props[PROP_IP] =
|
||||
g_param_spec_string ("own-ip",
|
||||
"Own IP",
|
||||
"Own IP for media and SDP",
|
||||
NULL,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT);
|
||||
|
||||
props[PROP_PIPELINE] =
|
||||
g_param_spec_object ("pipeline",
|
||||
"Pipeline",
|
||||
"Media pipeline for this call",
|
||||
CALLS_TYPE_SIP_MEDIA_PIPELINE,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||||
|
||||
props[PROP_MEDIA_ENCRYPTION] =
|
||||
g_param_spec_enum ("media-encryption",
|
||||
"Media encryption",
|
||||
"The media encryption mode",
|
||||
SIP_TYPE_MEDIA_ENCRYPTION,
|
||||
SIP_MEDIA_ENCRYPTION_NONE,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_init (CallsSipCall *self)
|
||||
{
|
||||
self->manager = calls_sip_media_manager_default ();
|
||||
self->sdp_crypto_context = calls_sdp_crypto_context_new ();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calls_sip_call_setup_remote_media_connection:
|
||||
* @self: A #CallsSipCall
|
||||
* @remote: The remote host
|
||||
* @port_rtp: The RTP port on the remote host
|
||||
* @port_rtcp: The RTCP port on the remote host
|
||||
*/
|
||||
void
|
||||
calls_sip_call_setup_remote_media_connection (CallsSipCall *self,
|
||||
const char *remote,
|
||||
guint port_rtp,
|
||||
guint port_rtcp)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_CALL (self));
|
||||
|
||||
g_free (self->remote);
|
||||
self->remote = g_strdup (remote);
|
||||
self->rport_rtp = port_rtp;
|
||||
self->rport_rtcp = port_rtcp;
|
||||
|
||||
g_debug ("Setting remote ports: RTP/RTCP %u/%u",
|
||||
self->rport_rtp, self->rport_rtcp);
|
||||
|
||||
g_object_set (self->pipeline,
|
||||
"remote", self->remote,
|
||||
"rport-rtp", self->rport_rtp,
|
||||
"rport-rtcp", self->rport_rtcp,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_call_activate_media:
|
||||
* @self: A #CallsSipCall
|
||||
* @enabled: %TRUE to enable the media pipeline, %FALSE to disable
|
||||
*
|
||||
* Controls the state of the #CallsSipMediaPipeline
|
||||
*/
|
||||
void
|
||||
calls_sip_call_activate_media (CallsSipCall *self,
|
||||
gboolean enabled)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_CALL (self));
|
||||
|
||||
/* when hanging up an incoming call the pipeline has not yet been setup */
|
||||
if (self->pipeline == NULL && !enabled)
|
||||
return;
|
||||
g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self->pipeline));
|
||||
|
||||
if (enabled) {
|
||||
gboolean negotiated = calls_sdp_crypto_context_get_is_negotiated (self->sdp_crypto_context);
|
||||
|
||||
if (negotiated) {
|
||||
calls_srtp_crypto_attribute *remote_crypto =
|
||||
calls_sdp_crypto_context_get_remote_crypto (self->sdp_crypto_context);
|
||||
calls_srtp_crypto_attribute *local_crypto =
|
||||
calls_sdp_crypto_context_get_local_crypto (self->sdp_crypto_context);
|
||||
|
||||
calls_sip_media_pipeline_set_crypto (self->pipeline, local_crypto, remote_crypto);
|
||||
} else {
|
||||
if (self->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
|
||||
g_warning ("Encryption is forced, but parameters were not negotiated! Aborting");
|
||||
return;
|
||||
} else if (self->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
|
||||
g_debug ("No encryption parameters negotiated, continuing unencrypted");
|
||||
}
|
||||
}
|
||||
|
||||
if (calls_sip_media_pipeline_get_state (self->pipeline) ==
|
||||
CALLS_MEDIA_PIPELINE_STATE_NEED_CODEC) {
|
||||
MediaCodecInfo *codec = (MediaCodecInfo *) self->codecs->data;
|
||||
|
||||
g_debug ("Setting codec '%s' for pipeline", codec->name);
|
||||
calls_sip_media_pipeline_set_codec (self->pipeline, codec);
|
||||
}
|
||||
|
||||
calls_sip_media_pipeline_start (self->pipeline);
|
||||
} else {
|
||||
calls_sip_media_pipeline_stop (self->pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CallsSipCall *
|
||||
calls_sip_call_new (const gchar *id,
|
||||
gboolean inbound,
|
||||
const char *own_ip,
|
||||
CallsSipMediaPipeline *pipeline,
|
||||
SipMediaEncryption media_encryption,
|
||||
nua_handle_t *handle)
|
||||
{
|
||||
g_return_val_if_fail (id, NULL);
|
||||
|
||||
return g_object_new (CALLS_TYPE_SIP_CALL,
|
||||
"id", id,
|
||||
"inbound", inbound,
|
||||
"own-ip", own_ip,
|
||||
"pipeline", pipeline,
|
||||
"media-encryption", media_encryption,
|
||||
"nua-handle", handle,
|
||||
"call-type", CALLS_CALL_TYPE_SIP_VOICE,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_call_set_codecs:
|
||||
* @self: A #CallsSipCall
|
||||
* @codecs: A #GList of #MediaCodecInfo elements
|
||||
*
|
||||
* Set the supported codecs. This is used when answering the call
|
||||
*/
|
||||
void
|
||||
calls_sip_call_set_codecs (CallsSipCall *self,
|
||||
GList *codecs)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_CALL (self));
|
||||
g_return_if_fail (codecs);
|
||||
|
||||
g_list_free (self->codecs);
|
||||
self->codecs = g_list_copy (codecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_call_get_sdp_crypto_context:
|
||||
* @self: A #CallsSipCall
|
||||
*
|
||||
* Returns: (transfer full): The #CallsSdpCryptoContext of this call
|
||||
*/
|
||||
CallsSdpCryptoContext *
|
||||
calls_sip_call_get_sdp_crypto_context (CallsSipCall *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_CALL (self), NULL);
|
||||
|
||||
return g_object_ref (self->sdp_crypto_context);
|
||||
}
|
||||
59
plugins/provider/sip/calls-sip-call.h
Normal file
59
plugins/provider/sip/calls-sip-call.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-call.h"
|
||||
#include "calls-sdp-crypto-context.h"
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <sofia-sip/nua.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_CALL (calls_sip_call_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipCall, calls_sip_call, CALLS, SIP_CALL, CallsCall)
|
||||
|
||||
CallsSipCall *calls_sip_call_new (const char *number,
|
||||
gboolean inbound,
|
||||
const char *own_ip,
|
||||
CallsSipMediaPipeline *pipeline,
|
||||
SipMediaEncryption encryption,
|
||||
nua_handle_t *handle);
|
||||
void calls_sip_call_setup_remote_media_connection (CallsSipCall *self,
|
||||
const char *remote,
|
||||
guint port_rtp,
|
||||
guint port_rtcp);
|
||||
void calls_sip_call_activate_media (CallsSipCall *self,
|
||||
gboolean enabled);
|
||||
void calls_sip_call_set_state (CallsSipCall *self,
|
||||
CallsCallState state);
|
||||
void calls_sip_call_set_codecs (CallsSipCall *self,
|
||||
GList *codecs);
|
||||
CallsSdpCryptoContext *calls_sip_call_get_sdp_crypto_context (CallsSipCall *self);
|
||||
|
||||
G_END_DECLS
|
||||
405
plugins/provider/sip/calls-sip-media-manager.c
Normal file
405
plugins/provider/sip/calls-sip-media-manager.c
Normal file
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 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 "CallsSipMediaManager"
|
||||
|
||||
#include "calls-settings.h"
|
||||
#include "calls-sip-media-manager.h"
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "calls-srtp-utils.h"
|
||||
#include "gst-rfc3551.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
|
||||
/**
|
||||
* SECTION:sip-media-manager
|
||||
* @short_description: The media manager singleton
|
||||
* @Title: CallsSipMediaManager
|
||||
*
|
||||
* #CallsSipMediaManager is mainly responsible for generating appropriate
|
||||
* SDP messages for the set of supported codecs. It also holds a list of
|
||||
* #CallsSipMediaPipeline objects that are ready to be used.
|
||||
*/
|
||||
|
||||
typedef struct _CallsSipMediaManager {
|
||||
GObject parent;
|
||||
|
||||
int address_family;
|
||||
struct addrinfo hints;
|
||||
|
||||
CallsSettings *settings;
|
||||
GList *preferred_codecs;
|
||||
GListStore *pipelines;
|
||||
} CallsSipMediaManager;
|
||||
|
||||
G_DEFINE_TYPE (CallsSipMediaManager, calls_sip_media_manager, G_TYPE_OBJECT);
|
||||
|
||||
|
||||
static const char *
|
||||
get_address_family_string (CallsSipMediaManager *self,
|
||||
const char *ip)
|
||||
{
|
||||
struct addrinfo *result = NULL;
|
||||
const char *family;
|
||||
|
||||
if (getaddrinfo (ip, NULL, &self->hints, &result) != 0) {
|
||||
g_warning ("Cannot parse session IP %s", ip);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* check if IP is IPv4 or IPv6. We need to specify this in the c= line of SDP */
|
||||
self->address_family = result->ai_family;
|
||||
|
||||
if (result->ai_family == AF_INET)
|
||||
family = "IP4";
|
||||
else if (result->ai_family == AF_INET6)
|
||||
family = "IP6";
|
||||
else
|
||||
family = NULL;
|
||||
|
||||
freeaddrinfo (result);
|
||||
|
||||
return family;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_notify_preferred_audio_codecs (CallsSipMediaManager *self)
|
||||
{
|
||||
GList *supported_codecs;
|
||||
|
||||
g_auto (GStrv) settings_codec_preference = NULL;
|
||||
|
||||
g_assert (CALLS_IS_SIP_MEDIA_MANAGER (self));
|
||||
|
||||
g_clear_list (&self->preferred_codecs, NULL);
|
||||
supported_codecs = media_codecs_get_candidates ();
|
||||
|
||||
if (!supported_codecs) {
|
||||
g_warning ("There aren't any supported codecs installed on your system");
|
||||
return;
|
||||
}
|
||||
|
||||
settings_codec_preference = calls_settings_get_preferred_audio_codecs (self->settings);
|
||||
|
||||
if (!settings_codec_preference) {
|
||||
g_debug ("No audio codec preference set. Using all supported codecs");
|
||||
self->preferred_codecs = supported_codecs;
|
||||
return;
|
||||
}
|
||||
|
||||
for (guint i = 0; settings_codec_preference[i] != NULL; i++) {
|
||||
MediaCodecInfo *codec = media_codec_by_name (settings_codec_preference[i]);
|
||||
|
||||
if (!codec) {
|
||||
g_debug ("Did not find audio codec %s", settings_codec_preference[i]);
|
||||
continue;
|
||||
}
|
||||
if (media_codec_available_in_gst (codec))
|
||||
self->preferred_codecs = g_list_append (self->preferred_codecs, codec);
|
||||
}
|
||||
|
||||
if (!self->preferred_codecs) {
|
||||
g_warning ("Cannot satisfy audio codec preference, "
|
||||
"falling back to all supported codecs");
|
||||
self->preferred_codecs = supported_codecs;
|
||||
} else {
|
||||
g_list_free (supported_codecs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_new_pipeline (CallsSipMediaManager *self)
|
||||
{
|
||||
CallsSipMediaPipeline *pipeline;
|
||||
|
||||
g_assert (CALLS_IS_SIP_MEDIA_MANAGER (self));
|
||||
|
||||
pipeline = calls_sip_media_pipeline_new (NULL);
|
||||
g_list_store_append (self->pipelines, pipeline);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_media_manager_finalize (GObject *object)
|
||||
{
|
||||
CallsSipMediaManager *self = CALLS_SIP_MEDIA_MANAGER (object);
|
||||
|
||||
g_list_free (self->preferred_codecs);
|
||||
g_object_unref (self->pipelines);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_media_manager_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_media_manager_class_init (CallsSipMediaManagerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = calls_sip_media_manager_finalize;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_media_manager_init (CallsSipMediaManager *self)
|
||||
{
|
||||
if (!gst_is_initialized ())
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
self->settings = calls_settings_get_default ();
|
||||
g_signal_connect_swapped (self->settings,
|
||||
"notify::preferred-audio-codecs",
|
||||
G_CALLBACK (on_notify_preferred_audio_codecs),
|
||||
self);
|
||||
on_notify_preferred_audio_codecs (self);
|
||||
|
||||
/* Hints are used with getaddrinfo() when setting the session IP */
|
||||
self->hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_NUMERICHOST;
|
||||
self->hints.ai_family = AF_UNSPEC;
|
||||
|
||||
self->pipelines = g_list_store_new (CALLS_TYPE_SIP_MEDIA_PIPELINE);
|
||||
|
||||
add_new_pipeline (self);
|
||||
}
|
||||
|
||||
|
||||
/* Public functions */
|
||||
|
||||
CallsSipMediaManager *
|
||||
calls_sip_media_manager_default (void)
|
||||
{
|
||||
static CallsSipMediaManager *instance = NULL;
|
||||
|
||||
if (instance == NULL) {
|
||||
g_debug ("Creating CallsSipMediaManager");
|
||||
instance = g_object_new (CALLS_TYPE_SIP_MEDIA_MANAGER, NULL);
|
||||
g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *) &instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_get_capabilities:
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
* @port: Should eventually come from the ICE stack
|
||||
* @crypto_attributes: A #GList of #calls_srtp_crypto_attribute
|
||||
* @supported_codecs: A #GList of #MediaCodecInfo
|
||||
*
|
||||
* Returns: (transfer full): string describing capabilities
|
||||
* to be used in the session description (SDP)
|
||||
*/
|
||||
char *
|
||||
calls_sip_media_manager_get_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes,
|
||||
GList *supported_codecs)
|
||||
{
|
||||
char *payload_type = crypto_attributes ? "SAVP" : "AVP";
|
||||
|
||||
g_autoptr (GString) media_line = NULL;
|
||||
g_autoptr (GString) attribute_lines = NULL;
|
||||
GList *node;
|
||||
const char *address_family_string;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
media_line = g_string_new (NULL);
|
||||
attribute_lines = g_string_new (NULL);
|
||||
|
||||
if (supported_codecs == NULL) {
|
||||
g_warning ("No supported codecs found. Can't build meaningful SDP message");
|
||||
g_string_append_printf (media_line, "m=audio 0 RTP/AVP");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* media lines look f.e like "audio 31337 RTP/AVP 9 8 0" */
|
||||
g_string_append_printf (media_line,
|
||||
"m=audio %d RTP/%s", rtp_port, payload_type);
|
||||
|
||||
for (node = supported_codecs; node != NULL; node = node->next) {
|
||||
MediaCodecInfo *codec = node->data;
|
||||
|
||||
g_string_append_printf (media_line, " %u", codec->payload_id);
|
||||
g_string_append_printf (attribute_lines,
|
||||
"a=rtpmap:%u %s/%u%s",
|
||||
codec->payload_id,
|
||||
codec->name,
|
||||
codec->clock_rate,
|
||||
"\r\n");
|
||||
}
|
||||
|
||||
for (node = crypto_attributes; node != NULL; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autofree char *crypto_line =
|
||||
calls_srtp_print_sdp_crypto_attribute(attr, &error);
|
||||
|
||||
if (!crypto_line) {
|
||||
g_warning ("Could not print SDP crypto line for tag %d: %s", attr->tag, error->message);
|
||||
continue;
|
||||
}
|
||||
g_string_append_printf (attribute_lines, "%s\r\n", crypto_line);
|
||||
}
|
||||
|
||||
g_string_append_printf (attribute_lines, "a=rtcp:%d\r\n", rtcp_port);
|
||||
|
||||
done:
|
||||
if (own_ip && *own_ip)
|
||||
address_family_string = get_address_family_string (self, own_ip);
|
||||
|
||||
if (own_ip && *own_ip && address_family_string)
|
||||
return g_strdup_printf ("v=0\r\n"
|
||||
"c=IN %s %s\r\n"
|
||||
"%s\r\n"
|
||||
"%s\r\n",
|
||||
address_family_string,
|
||||
own_ip,
|
||||
media_line->str,
|
||||
attribute_lines->str);
|
||||
else
|
||||
return g_strdup_printf ("v=0\r\n"
|
||||
"%s\r\n"
|
||||
"%s\r\n",
|
||||
media_line->str,
|
||||
attribute_lines->str);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_static_capabilities:
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
* @rtp_port: Port to use for RTP. Should eventually come from the ICE stack
|
||||
* @rtcp_port: Port to use for RTCP.Should eventually come from the ICE stack
|
||||
* @crypto_attributes: A #GList of #calls_srtp_crypto_attribute
|
||||
*
|
||||
* Returns: (transfer full): string describing capabilities
|
||||
* to be used in the session description (SDP)
|
||||
*/
|
||||
char *
|
||||
calls_sip_media_manager_static_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
return calls_sip_media_manager_get_capabilities (self,
|
||||
own_ip,
|
||||
rtp_port,
|
||||
rtcp_port,
|
||||
crypto_attributes,
|
||||
self->preferred_codecs);
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_codec_candiates:
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
*
|
||||
* Returns: (transfer none): A #GList of supported
|
||||
* #MediaCodecInfo
|
||||
*/
|
||||
GList *
|
||||
calls_sip_media_manager_codec_candidates (CallsSipMediaManager *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
return self->preferred_codecs;
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_get_codecs_from_sdp
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
* @sdp: A #sdp_media_t media description
|
||||
*
|
||||
* Returns: (transfer container): A #GList of codecs found in the
|
||||
* SDP message
|
||||
*/
|
||||
GList *
|
||||
calls_sip_media_manager_get_codecs_from_sdp (CallsSipMediaManager *self,
|
||||
sdp_media_t *sdp_media)
|
||||
{
|
||||
GList *codecs = NULL;
|
||||
sdp_rtpmap_t *rtpmap = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
g_return_val_if_fail (sdp_media, NULL);
|
||||
|
||||
if (sdp_media->m_type != sdp_media_audio) {
|
||||
g_warning ("Only the 'audio' media type is supported");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (rtpmap = sdp_media->m_rtpmaps; rtpmap != NULL; rtpmap = rtpmap->rm_next) {
|
||||
MediaCodecInfo *codec = media_codec_by_payload_id (rtpmap->rm_pt);
|
||||
if (codec)
|
||||
codecs = g_list_append (codecs, codec);
|
||||
}
|
||||
|
||||
if (sdp_media->m_next != NULL)
|
||||
g_warning ("Currently only a single media session is supported");
|
||||
|
||||
if (codecs == NULL)
|
||||
g_warning ("Did not find any common codecs");
|
||||
|
||||
return codecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_media_manager_get_pipeline:
|
||||
* @self: A #CallsSipMediaManager
|
||||
*
|
||||
* Returns: (transfer full): A #CallsSipMediaPipeline
|
||||
*/
|
||||
CallsSipMediaPipeline *
|
||||
calls_sip_media_manager_get_pipeline (CallsSipMediaManager *self)
|
||||
{
|
||||
g_autoptr (CallsSipMediaPipeline) pipeline = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
pipeline = g_list_model_get_item (G_LIST_MODEL (self->pipelines), 0);
|
||||
|
||||
g_list_store_remove (self->pipelines, 0);
|
||||
|
||||
/* add a pipeline for the one we just removed */
|
||||
add_new_pipeline (self);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
59
plugins/provider/sip/calls-sip-media-manager.h
Normal file
59
plugins/provider/sip/calls-sip-media-manager.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "gst-rfc3551.h"
|
||||
|
||||
#include <sofia-sip/sdp.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_MEDIA_MANAGER (calls_sip_media_manager_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipMediaManager, calls_sip_media_manager, CALLS, SIP_MEDIA_MANAGER, GObject);
|
||||
|
||||
|
||||
CallsSipMediaManager *calls_sip_media_manager_default (void);
|
||||
gchar *calls_sip_media_manager_get_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes,
|
||||
GList *supported_codecs);
|
||||
gchar *calls_sip_media_manager_static_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes);
|
||||
gboolean calls_sip_media_manager_supports_media (CallsSipMediaManager *self,
|
||||
const char *media_type);
|
||||
GList *calls_sip_media_manager_codec_candidates (CallsSipMediaManager *self);
|
||||
GList *calls_sip_media_manager_get_codecs_from_sdp (CallsSipMediaManager *self,
|
||||
sdp_media_t *sdp_media);
|
||||
CallsSipMediaPipeline *calls_sip_media_manager_get_pipeline (CallsSipMediaManager *self);
|
||||
|
||||
G_END_DECLS
|
||||
1410
plugins/provider/sip/calls-sip-media-pipeline.c
Normal file
1410
plugins/provider/sip/calls-sip-media-pipeline.c
Normal file
File diff suppressed because it is too large
Load Diff
82
plugins/provider/sip/calls-sip-media-pipeline.h
Normal file
82
plugins/provider/sip/calls-sip-media-pipeline.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-srtp-utils.h"
|
||||
#include "gst-rfc3551.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* CallsMediaPipelineState:
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_UNKNOWN: Default state for new pipelines
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_ERROR: Pipeline is in an error state
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_INITIALIZING: Pipeline is initializing
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_NEED_CODEC: Pipeline was initialized and needs a codec set
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_READY: Pipeline is ready to be set into playing state
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PLAY_PENDING: Request to start pipeline pending
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PLAYING: Pipeline is currently playing
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PAUSE_PENDING: Request to pause pipeline pending
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PAUSED: Pipeline is currently paused
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_STOP_PENDING: Request to stop pipeline pending
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_STOPPED: Pipeline has stopped playing (f.e. received BYE packet)
|
||||
*/
|
||||
typedef enum {
|
||||
CALLS_MEDIA_PIPELINE_STATE_UNKNOWN = 0,
|
||||
CALLS_MEDIA_PIPELINE_STATE_ERROR,
|
||||
CALLS_MEDIA_PIPELINE_STATE_INITIALIZING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_NEED_CODEC,
|
||||
CALLS_MEDIA_PIPELINE_STATE_READY,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PLAY_PENDING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PLAYING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PAUSE_PENDING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PAUSED,
|
||||
CALLS_MEDIA_PIPELINE_STATE_STOP_PENDING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_STOPPED
|
||||
} CallsMediaPipelineState;
|
||||
|
||||
|
||||
#define CALLS_TYPE_SIP_MEDIA_PIPELINE (calls_sip_media_pipeline_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipMediaPipeline, calls_sip_media_pipeline, CALLS, SIP_MEDIA_PIPELINE, GObject)
|
||||
|
||||
|
||||
CallsSipMediaPipeline* calls_sip_media_pipeline_new (MediaCodecInfo *codec);
|
||||
void calls_sip_media_pipeline_set_codec (CallsSipMediaPipeline *self,
|
||||
MediaCodecInfo *info);
|
||||
void calls_sip_media_pipeline_set_crypto (CallsSipMediaPipeline *self,
|
||||
calls_srtp_crypto_attribute *crypto_own,
|
||||
calls_srtp_crypto_attribute *crypto_theirs);
|
||||
void calls_sip_media_pipeline_start (CallsSipMediaPipeline *self);
|
||||
void calls_sip_media_pipeline_stop (CallsSipMediaPipeline *self);
|
||||
void calls_sip_media_pipeline_pause (CallsSipMediaPipeline *self,
|
||||
gboolean pause);
|
||||
int calls_sip_media_pipeline_get_rtp_port (CallsSipMediaPipeline *self);
|
||||
int calls_sip_media_pipeline_get_rtcp_port (CallsSipMediaPipeline *self);
|
||||
CallsMediaPipelineState calls_sip_media_pipeline_get_state (CallsSipMediaPipeline *self);
|
||||
|
||||
G_END_DECLS
|
||||
1743
plugins/provider/sip/calls-sip-origin.c
Normal file
1743
plugins/provider/sip/calls-sip-origin.c
Normal file
File diff suppressed because it is too large
Load Diff
48
plugins/provider/sip/calls-sip-origin.h
Normal file
48
plugins/provider/sip/calls-sip-origin.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_ORIGIN (calls_sip_origin_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipOrigin, calls_sip_origin, CALLS, SIP_ORIGIN, GObject)
|
||||
|
||||
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,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean use_for_tel,
|
||||
gboolean auto_connect);
|
||||
|
||||
G_END_DECLS
|
||||
877
plugins/provider/sip/calls-sip-provider.c
Normal file
877
plugins/provider/sip/calls-sip-provider.c
Normal file
@@ -0,0 +1,877 @@
|
||||
/*
|
||||
* 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 "CallsSipProvider"
|
||||
|
||||
#define SIP_ACCOUNT_FILE "sip-account.cfg"
|
||||
#define CALLS_PROTOCOL_SIP_STR "sip"
|
||||
|
||||
#include "calls-account-provider.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-provider.h"
|
||||
#include "calls-secret-store.h"
|
||||
#include "calls-sip-account-widget.h"
|
||||
#include "calls-sip-enums.h"
|
||||
#include "calls-sip-origin.h"
|
||||
#include "calls-sip-provider.h"
|
||||
#include "calls-sip-util.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <libpeas/peas.h>
|
||||
#include <sofia-sip/nua.h>
|
||||
#include <sofia-sip/su_glib.h>
|
||||
|
||||
static const char * const supported_protocols[] = {
|
||||
"tel",
|
||||
"sip",
|
||||
"sips",
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* SECTION:sip-provider
|
||||
* @short_description: A #CallsProvider for the SIP protocol
|
||||
* @Title: CallsSipProvider
|
||||
*
|
||||
* #CallsSipProvider is derived from #CallsProvider and is responsible
|
||||
* for setting up the sofia-sip stack.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_SIP_STATE,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
struct _CallsSipProvider {
|
||||
CallsProvider parent_instance;
|
||||
|
||||
GListStore *origins;
|
||||
/* SIP */
|
||||
CallsSipContext *ctx;
|
||||
SipEngineState sip_state;
|
||||
|
||||
gboolean use_memory_backend;
|
||||
gchar *filename;
|
||||
|
||||
CallsSipAccountWidget *account_widget;
|
||||
};
|
||||
|
||||
static void calls_sip_provider_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
static void calls_sip_provider_account_provider_interface_init (CallsAccountProviderInterface *iface);
|
||||
|
||||
#ifdef FOR_TESTING
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE
|
||||
(CallsSipProvider, calls_sip_provider, CALLS_TYPE_PROVIDER,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_sip_provider_message_source_interface_init)
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_ACCOUNT_PROVIDER,
|
||||
calls_sip_provider_account_provider_interface_init))
|
||||
|
||||
#else
|
||||
|
||||
G_DEFINE_DYNAMIC_TYPE_EXTENDED
|
||||
(CallsSipProvider, calls_sip_provider, CALLS_TYPE_PROVIDER, 0,
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_sip_provider_message_source_interface_init)
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_ACCOUNT_PROVIDER,
|
||||
calls_sip_provider_account_provider_interface_init))
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
|
||||
typedef struct {
|
||||
CallsSipProvider *provider;
|
||||
GKeyFile *key_file;
|
||||
char *name;
|
||||
} SipOriginLoadData;
|
||||
|
||||
static void
|
||||
on_origin_pw_looked_up (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SipOriginLoadData *data;
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autofree char *id = NULL;
|
||||
g_autofree char *name = NULL;
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
g_autofree char *password = NULL;
|
||||
g_autofree char *display_name = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
gint port = 0;
|
||||
gint local_port = 0;
|
||||
gboolean auto_connect = TRUE;
|
||||
gboolean direct_mode = FALSE;
|
||||
gboolean can_tel = FALSE;
|
||||
SipMediaEncryption media_encryption = SIP_MEDIA_ENCRYPTION_NONE;
|
||||
|
||||
g_assert (user_data);
|
||||
|
||||
data = user_data;
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "Id", NULL))
|
||||
id = g_key_file_get_string (data->key_file, data->name, "Id", NULL);
|
||||
else
|
||||
id = g_strdup (data->name);
|
||||
|
||||
host = g_key_file_get_string (data->key_file, data->name, "Host", NULL);
|
||||
user = g_key_file_get_string (data->key_file, data->name, "User", NULL);
|
||||
display_name = g_key_file_get_string (data->key_file, data->name, "DisplayName", NULL);
|
||||
protocol = g_key_file_get_string (data->key_file, data->name, "Protocol", NULL);
|
||||
port = g_key_file_get_integer (data->key_file, data->name, "Port", NULL);
|
||||
display_name = g_key_file_get_string (data->key_file, data->name, "DisplayName", NULL);
|
||||
local_port = g_key_file_get_integer (data->key_file, data->name, "LocalPort", NULL);
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "AutoConnect", NULL))
|
||||
auto_connect = g_key_file_get_boolean (data->key_file, data->name, "AutoConnect", NULL);
|
||||
|
||||
if (protocol == NULL)
|
||||
protocol = g_strdup ("UDP");
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "DirectMode", NULL))
|
||||
direct_mode = g_key_file_get_boolean (data->key_file, data->name, "DirectMode", NULL);
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "CanTel", NULL))
|
||||
can_tel =
|
||||
g_key_file_get_boolean (data->key_file, data->name, "CanTel", NULL);
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "MediaEncryption", NULL))
|
||||
media_encryption =
|
||||
(SipMediaEncryption) g_key_file_get_integer (data->key_file, data->name, "MediaEncryption", NULL);
|
||||
|
||||
/* PW */
|
||||
password = secret_password_lookup_finish (result, &error);
|
||||
if (!direct_mode && error) {
|
||||
g_warning ("Could not lookup password: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
#define IS_NULL_OR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
|
||||
if (!direct_mode &&
|
||||
(IS_NULL_OR_EMPTY (host) ||
|
||||
IS_NULL_OR_EMPTY (user) ||
|
||||
IS_NULL_OR_EMPTY (password))) {
|
||||
g_warning ("Host, user and password must not be empty");
|
||||
|
||||
return;
|
||||
}
|
||||
#undef IS_NULL_OR_EMPTY
|
||||
|
||||
calls_sip_provider_add_origin_full (data->provider,
|
||||
id,
|
||||
host,
|
||||
user,
|
||||
password,
|
||||
display_name,
|
||||
protocol,
|
||||
port,
|
||||
media_encryption,
|
||||
auto_connect,
|
||||
direct_mode,
|
||||
local_port,
|
||||
can_tel,
|
||||
FALSE);
|
||||
}
|
||||
static void
|
||||
new_origin_from_keyfile_secret (CallsSipProvider *self,
|
||||
GKeyFile *key_file,
|
||||
const char *name)
|
||||
{
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
SipOriginLoadData *data;
|
||||
|
||||
g_assert (CALLS_IS_SIP_PROVIDER (self));
|
||||
g_assert (key_file);
|
||||
g_assert (name);
|
||||
|
||||
if (!g_key_file_has_group (key_file, name)) {
|
||||
g_warning ("Keyfile has no group %s", name);
|
||||
return;
|
||||
}
|
||||
|
||||
host = g_key_file_get_string (key_file, name, "Host", NULL);
|
||||
user = g_key_file_get_string (key_file, name, "User", NULL);
|
||||
|
||||
data = g_new0 (SipOriginLoadData, 1);
|
||||
data->provider = self;
|
||||
g_key_file_ref (key_file);
|
||||
data->key_file = key_file;
|
||||
data->name = g_strdup (name);
|
||||
|
||||
secret_password_lookup (calls_secret_get_schema (), NULL,
|
||||
on_origin_pw_looked_up, data,
|
||||
CALLS_SERVER_ATTRIBUTE, host,
|
||||
CALLS_USERNAME_ATTRIBUTE, user,
|
||||
CALLS_PROTOCOL_ATTRIBUTE, CALLS_PROTOCOL_SIP_STR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_origin_pw_cleared (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!secret_password_clear_finish (result, &error))
|
||||
g_warning ("Could not delete the password in the keyring: %s",
|
||||
error ? error->message : "No reason given");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
origin_pw_delete_secret (CallsSipOrigin *origin)
|
||||
{
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
||||
|
||||
g_object_get (origin,
|
||||
"host", &host,
|
||||
"user", &user,
|
||||
NULL);
|
||||
|
||||
secret_password_clear (calls_secret_get_schema (), NULL, on_origin_pw_cleared, NULL,
|
||||
CALLS_SERVER_ATTRIBUTE, host,
|
||||
CALLS_USERNAME_ATTRIBUTE, user,
|
||||
CALLS_PROTOCOL_ATTRIBUTE, CALLS_PROTOCOL_SIP_STR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_origin_pw_saved (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!secret_password_store_finish (result, &error)) {
|
||||
g_warning ("Could not store the password in the keyring: %s",
|
||||
error ? error->message : "No reason given");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
origin_to_keyfile (CallsSipOrigin *origin,
|
||||
GKeyFile *key_file,
|
||||
const char *name)
|
||||
{
|
||||
g_autofree char *id = NULL;
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
g_autofree char *password = NULL;
|
||||
g_autofree char *display_name = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
g_autofree char *label_secret = NULL;
|
||||
gint port;
|
||||
gint local_port;
|
||||
gboolean auto_connect;
|
||||
gboolean direct_mode;
|
||||
gboolean can_tel;
|
||||
SipMediaEncryption media_encryption;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
||||
g_assert (key_file);
|
||||
|
||||
g_object_get (origin,
|
||||
"id", &id,
|
||||
"host", &host,
|
||||
"user", &user,
|
||||
"password", &password,
|
||||
"display-name", &display_name,
|
||||
"transport-protocol", &protocol,
|
||||
"port", &port,
|
||||
"auto-connect", &auto_connect,
|
||||
"direct-mode", &direct_mode,
|
||||
"local-port", &local_port,
|
||||
"can-tel", &can_tel,
|
||||
"media-encryption", &media_encryption,
|
||||
NULL);
|
||||
|
||||
g_key_file_set_string (key_file, name, "Id", id);
|
||||
g_key_file_set_string (key_file, name, "Host", host);
|
||||
g_key_file_set_string (key_file, name, "User", user);
|
||||
g_key_file_set_string (key_file, name, "DisplayName", display_name ?: "");
|
||||
g_key_file_set_string (key_file, name, "Protocol", protocol);
|
||||
g_key_file_set_integer (key_file, name, "Port", port);
|
||||
g_key_file_set_boolean (key_file, name, "AutoConnect", auto_connect);
|
||||
g_key_file_set_boolean (key_file, name, "DirectMode", direct_mode);
|
||||
g_key_file_set_integer (key_file, name, "LocalPort", local_port);
|
||||
g_key_file_set_boolean (key_file, name, "CanTel", can_tel);
|
||||
g_key_file_set_integer (key_file, name, "MediaEncryption", media_encryption);
|
||||
|
||||
label_secret = g_strdup_printf ("Calls Password for %s", id);
|
||||
|
||||
/* save to keyring */
|
||||
secret_password_store (calls_secret_get_schema (), NULL, label_secret, password,
|
||||
NULL, on_origin_pw_saved, NULL,
|
||||
CALLS_SERVER_ATTRIBUTE, host,
|
||||
CALLS_USERNAME_ATTRIBUTE, user,
|
||||
CALLS_PROTOCOL_ATTRIBUTE, CALLS_PROTOCOL_SIP_STR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
calls_sip_provider_get_name (CallsProvider *provider)
|
||||
{
|
||||
return "SIP provider";
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
calls_sip_provider_get_status (CallsProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
switch (self->sip_state) {
|
||||
case SIP_ENGINE_ERROR:
|
||||
return "Error";
|
||||
|
||||
case SIP_ENGINE_READY:
|
||||
return "Normal";
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
||||
static GListModel *
|
||||
calls_sip_provider_get_origins (CallsProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
return G_LIST_MODEL (self->origins);
|
||||
}
|
||||
|
||||
static const char *const *
|
||||
calls_sip_provider_get_protocols (CallsProvider *provider)
|
||||
{
|
||||
return supported_protocols;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_deinit_sip (CallsSipProvider *self)
|
||||
{
|
||||
GSource *gsource;
|
||||
|
||||
if (self->sip_state == SIP_ENGINE_NULL)
|
||||
return;
|
||||
|
||||
/* clean up sofia */
|
||||
if (!self->ctx)
|
||||
goto bail;
|
||||
|
||||
|
||||
if (self->ctx->root) {
|
||||
gsource = su_glib_root_gsource (self->ctx->root);
|
||||
g_source_destroy (gsource);
|
||||
su_root_destroy (self->ctx->root);
|
||||
|
||||
if (su_home_unref (self->ctx->home) != 1)
|
||||
g_warning ("Error in su_home_unref ()");
|
||||
}
|
||||
g_clear_pointer (&self->ctx, g_free);
|
||||
|
||||
bail:
|
||||
self->sip_state = SIP_ENGINE_NULL;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
calls_sip_provider_init_sofia (CallsSipProvider *self,
|
||||
GError **error)
|
||||
{
|
||||
GSource *gsource;
|
||||
|
||||
g_assert (CALLS_SIP_PROVIDER (self));
|
||||
|
||||
self->ctx = g_new0 (CallsSipContext, 1);
|
||||
if (self->ctx == NULL) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Could not allocate memory for the SIP context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (su_init () != su_success) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_init () failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (su_home_init (self->ctx->home) != su_success) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_home_init () failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
self->ctx->root = su_glib_root_create (self);
|
||||
if (self->ctx->root == NULL) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_glib_root_create () failed");
|
||||
goto err;
|
||||
}
|
||||
gsource = su_glib_root_gsource (self->ctx->root);
|
||||
if (gsource == NULL) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_glib_root_gsource () failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
g_source_attach (gsource, NULL);
|
||||
self->sip_state = SIP_ENGINE_READY;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]);
|
||||
return TRUE;
|
||||
|
||||
err:
|
||||
self->sip_state = SIP_ENGINE_ERROR;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_SIP_STATE:
|
||||
g_value_set_enum (value, self->sip_state);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_constructed (GObject *object)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (object);
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
const gchar *env_sip_test;
|
||||
|
||||
env_sip_test = g_getenv ("CALLS_SIP_TEST");
|
||||
if (env_sip_test && env_sip_test[0] != '\0')
|
||||
self->use_memory_backend = TRUE;
|
||||
|
||||
if (calls_sip_provider_init_sofia (self, &error)) {
|
||||
if (!self->use_memory_backend) {
|
||||
g_autoptr (GKeyFile) key_file = g_key_file_new ();
|
||||
|
||||
if (!g_key_file_load_from_file (key_file, self->filename, G_KEY_FILE_NONE, &error)) {
|
||||
g_debug ("Error loading key file: %s", error->message);
|
||||
goto out;
|
||||
}
|
||||
calls_sip_provider_load_accounts (self, key_file);
|
||||
}
|
||||
} else {
|
||||
g_warning ("Could not initialize sofia stack: %s", error->message);
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_provider_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_dispose (GObject *object)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (object);
|
||||
|
||||
g_list_store_remove_all (self->origins);
|
||||
g_clear_object (&self->origins);
|
||||
g_clear_object (&self->account_widget);
|
||||
|
||||
g_clear_pointer (&self->filename, g_free);
|
||||
|
||||
calls_sip_provider_deinit_sip (self);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_provider_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
#ifndef FOR_TESTING
|
||||
|
||||
static void
|
||||
calls_sip_provider_class_finalize (CallsSipProviderClass *klass)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
|
||||
static void
|
||||
calls_sip_provider_class_init (CallsSipProviderClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsProviderClass *provider_class = CALLS_PROVIDER_CLASS (klass);
|
||||
|
||||
object_class->constructed = calls_sip_provider_constructed;
|
||||
object_class->dispose = calls_sip_provider_dispose;
|
||||
object_class->get_property = calls_sip_provider_get_property;
|
||||
|
||||
provider_class->get_name = calls_sip_provider_get_name;
|
||||
provider_class->get_status = calls_sip_provider_get_status;
|
||||
provider_class->get_origins = calls_sip_provider_get_origins;
|
||||
provider_class->get_protocols = calls_sip_provider_get_protocols;
|
||||
|
||||
props[PROP_SIP_STATE] =
|
||||
g_param_spec_enum ("sip-state",
|
||||
"SIP state",
|
||||
"The state of the SIP engine",
|
||||
SIP_TYPE_ENGINE_STATE,
|
||||
SIP_ENGINE_NULL,
|
||||
G_PARAM_READABLE);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_account_widget (CallsSipProvider *self)
|
||||
{
|
||||
if (!self->account_widget) {
|
||||
self->account_widget = calls_sip_account_widget_new (self);
|
||||
g_object_ref_sink (self->account_widget);
|
||||
}
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
get_account_widget (CallsAccountProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
ensure_account_widget (self);
|
||||
return GTK_WIDGET (self->account_widget);
|
||||
}
|
||||
|
||||
static void
|
||||
add_new_account (CallsAccountProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
ensure_account_widget (self);
|
||||
calls_sip_account_widget_set_origin (self->account_widget, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
edit_account (CallsAccountProvider *provider,
|
||||
CallsAccount *account)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
ensure_account_widget (self);
|
||||
calls_sip_account_widget_set_origin (self->account_widget, CALLS_SIP_ORIGIN (account));
|
||||
}
|
||||
|
||||
static void
|
||||
calls_sip_provider_account_provider_interface_init (CallsAccountProviderInterface *iface)
|
||||
{
|
||||
iface->get_account_widget = get_account_widget;
|
||||
iface->add_new_account = add_new_account;
|
||||
iface->edit_account = edit_account;
|
||||
}
|
||||
|
||||
static void
|
||||
calls_sip_provider_init (CallsSipProvider *self)
|
||||
{
|
||||
g_autofree char *directory = NULL;
|
||||
const char *filename_env = g_getenv ("CALLS_SIP_ACCOUNT_FILE");
|
||||
const char *sip_test_env = g_getenv ("CALLS_SIP_TEST");
|
||||
|
||||
self->origins = g_list_store_new (CALLS_TYPE_ORIGIN);
|
||||
|
||||
if (filename_env && filename_env[0] != '\0')
|
||||
self->filename = g_strdup (filename_env);
|
||||
else
|
||||
self->filename = g_build_filename (g_get_user_config_dir (),
|
||||
APP_DATA_NAME,
|
||||
SIP_ACCOUNT_FILE,
|
||||
NULL);
|
||||
|
||||
if (!sip_test_env || sip_test_env[0] == '\0') {
|
||||
directory = g_path_get_dirname (self->filename);
|
||||
if (g_mkdir_with_parents (directory, 0750) == -1) {
|
||||
int err_save = errno;
|
||||
g_warning ("Failed to create directory '%s': %d\n"
|
||||
"Can not store credentials persistently!",
|
||||
directory, err_save);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_provider_add_origin:
|
||||
* @self: A #CallsSipProvider
|
||||
* @id: The id of the new origin (should be unique)
|
||||
* @host: The host to connect to
|
||||
* @user: The username to use
|
||||
* @password: The password to use
|
||||
* @display_name: The display name
|
||||
* @transport_protocol: The transport protocol to use, can be one of "UDP", "TCP" or "TLS"
|
||||
* @port: The port of the host
|
||||
* @media_encryption: A #SipMediaEncryption specifying if media should be encrypted
|
||||
* @store_credentials: Whether to store credentials for this origin to disk
|
||||
*
|
||||
* Adds a new origin (SIP account)
|
||||
*
|
||||
* Return: (transfer none): A #CallsSipOrigin
|
||||
*/
|
||||
CallsSipOrigin *
|
||||
calls_sip_provider_add_origin (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean store_credentials)
|
||||
{
|
||||
return calls_sip_provider_add_origin_full (self,
|
||||
id,
|
||||
host,
|
||||
user,
|
||||
password,
|
||||
display_name,
|
||||
transport_protocol,
|
||||
port,
|
||||
media_encryption,
|
||||
TRUE,
|
||||
FALSE,
|
||||
0,
|
||||
FALSE,
|
||||
store_credentials);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_provider_add_origin_full:
|
||||
* @self: A #CallsSipProvider
|
||||
* @id: The id of the new origin (should be unique)
|
||||
* @host: The host to connect to
|
||||
* @user: The username to use
|
||||
* @password: The password to use
|
||||
* @display_name: The display name
|
||||
* @transport_protocol: The transport protocol to use, can be one of "UDP", "TCP" or "TLS"
|
||||
* @port: The port of the host
|
||||
* @media_encryption: A #SipMediaEncryption specifying if media should be encrypted
|
||||
* @auto_connect: Whether to automatically try going online
|
||||
* @direct_mode: Whether to use direct connection mode. Useful when you don't want to
|
||||
* connect to a SIP server. Mostly useful for testing and debugging.
|
||||
* @can_tel: Whether this origin can be used for PSTN telephony
|
||||
* @store_credentials: Whether to store credentials for this origin to disk
|
||||
*
|
||||
* Adds a new origin (SIP account). If @direct_mode is %TRUE then @host, @user and
|
||||
* @password do not have to be set.
|
||||
*
|
||||
* Return: (transfer none): A #CallsSipOrigin
|
||||
*/
|
||||
CallsSipOrigin *
|
||||
calls_sip_provider_add_origin_full (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean auto_connect,
|
||||
gboolean direct_mode,
|
||||
gint local_port,
|
||||
gboolean can_tel,
|
||||
gboolean store_credentials)
|
||||
{
|
||||
g_autoptr (CallsSipOrigin) origin = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_PROVIDER (self), NULL);
|
||||
g_return_val_if_fail (id || *id, NULL);
|
||||
|
||||
/* direct-mode is mostly useful for testing without a SIP server */
|
||||
if (!direct_mode) {
|
||||
g_return_val_if_fail (host, NULL);
|
||||
g_return_val_if_fail (user, NULL);
|
||||
g_return_val_if_fail (password, NULL);
|
||||
}
|
||||
|
||||
if (transport_protocol) {
|
||||
g_return_val_if_fail (protocol_is_valid (transport_protocol), NULL);
|
||||
|
||||
protocol = g_ascii_strup (transport_protocol, -1);
|
||||
}
|
||||
|
||||
origin = g_object_new (CALLS_TYPE_SIP_ORIGIN,
|
||||
"id", id,
|
||||
"sip-context", self->ctx,
|
||||
"host", host,
|
||||
"user", user,
|
||||
"password", password,
|
||||
"display-name", display_name,
|
||||
"transport-protocol", protocol ?: "UDP",
|
||||
"port", port,
|
||||
"media-encryption", media_encryption,
|
||||
"auto-connect", auto_connect,
|
||||
"direct-mode", direct_mode,
|
||||
"local-port", local_port,
|
||||
"can-tel", can_tel,
|
||||
NULL);
|
||||
|
||||
g_list_store_append (self->origins, origin);
|
||||
|
||||
if (store_credentials && !self->use_memory_backend)
|
||||
calls_sip_provider_save_accounts_to_disk (self);
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sip_provider_remove_origin (CallsSipProvider *self,
|
||||
CallsSipOrigin *origin)
|
||||
{
|
||||
guint position;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_PROVIDER (self), FALSE);
|
||||
g_return_val_if_fail (CALLS_IS_SIP_ORIGIN (origin), FALSE);
|
||||
|
||||
if (g_list_store_find (self->origins, origin, &position)) {
|
||||
g_object_ref (origin);
|
||||
g_list_store_remove (self->origins, position);
|
||||
|
||||
if (!self->use_memory_backend) {
|
||||
origin_pw_delete_secret (origin);
|
||||
calls_sip_provider_save_accounts_to_disk (self);
|
||||
}
|
||||
g_object_unref (origin);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
CallsSipProvider *
|
||||
calls_sip_provider_new (void)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_SIP_PROVIDER, NULL);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_sip_provider_load_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_auto (GStrv) groups = NULL;
|
||||
|
||||
g_return_if_fail (CALLS_IS_SIP_PROVIDER (self));
|
||||
g_return_if_fail (key_file);
|
||||
|
||||
groups = g_key_file_get_groups (key_file, NULL);
|
||||
|
||||
for (gsize i = 0; groups[i] != NULL; i++) {
|
||||
new_origin_from_keyfile_secret (self, key_file, groups[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_sip_provider_save_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file)
|
||||
{
|
||||
guint n_origins;
|
||||
|
||||
g_return_if_fail (CALLS_IS_SIP_PROVIDER (self));
|
||||
g_return_if_fail (key_file);
|
||||
|
||||
n_origins = g_list_model_get_n_items (G_LIST_MODEL (self->origins));
|
||||
for (guint i = 0; i < n_origins; i++) {
|
||||
g_autoptr (CallsSipOrigin) origin =
|
||||
g_list_model_get_item (G_LIST_MODEL (self->origins), i);
|
||||
g_autofree char *group_name = g_strdup_printf ("sip-%02d", i);
|
||||
|
||||
origin_to_keyfile (origin, key_file, group_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sip_provider_save_accounts_to_disk (CallsSipProvider *self)
|
||||
{
|
||||
g_autoptr (GKeyFile) key_file = g_key_file_new ();
|
||||
g_autoptr (GError) error = NULL;
|
||||
gboolean saved = FALSE;
|
||||
|
||||
g_assert (CALLS_IS_SIP_PROVIDER (self));
|
||||
|
||||
calls_sip_provider_save_accounts (self, key_file);
|
||||
|
||||
if (!(saved = g_key_file_save_to_file (key_file, self->filename, &error)))
|
||||
g_warning ("Error saving keyfile to file %s: %s", self->filename, error->message);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
|
||||
#ifndef FOR_TESTING
|
||||
|
||||
G_MODULE_EXPORT void
|
||||
peas_register_types (PeasObjectModule *module)
|
||||
{
|
||||
calls_sip_provider_register_type (G_TYPE_MODULE (module));
|
||||
|
||||
peas_object_module_register_extension_type (module,
|
||||
CALLS_TYPE_PROVIDER,
|
||||
CALLS_TYPE_SIP_PROVIDER);
|
||||
}
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
73
plugins/provider/sip/calls-sip-provider.h
Normal file
73
plugins/provider/sip/calls-sip-provider.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-provider.h"
|
||||
#include "calls-sip-origin.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <libpeas/peas.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_PROVIDER (calls_sip_provider_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipProvider, calls_sip_provider, CALLS, SIP_PROVIDER, CallsProvider);
|
||||
|
||||
CallsSipProvider *calls_sip_provider_new (void);
|
||||
CallsSipOrigin *calls_sip_provider_add_origin (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean store_credentials);
|
||||
CallsSipOrigin *calls_sip_provider_add_origin_full (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean auto_connect,
|
||||
gboolean direct_mode,
|
||||
gint local_port,
|
||||
gboolean use_for_tel,
|
||||
gboolean store_credentials);
|
||||
gboolean calls_sip_provider_remove_origin (CallsSipProvider *self,
|
||||
CallsSipOrigin *origin);
|
||||
void calls_sip_provider_load_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file);
|
||||
void calls_sip_provider_save_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file);
|
||||
gboolean calls_sip_provider_save_accounts_to_disk (CallsSipProvider *self);
|
||||
void peas_register_types (PeasObjectModule *module);
|
||||
|
||||
G_END_DECLS
|
||||
63
plugins/provider/sip/calls-sip-util.c
Normal file
63
plugins/provider/sip/calls-sip-util.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
gboolean
|
||||
check_ipv6 (const char *host)
|
||||
{
|
||||
/* TODO DNS SRV records to determine whether or not to use IPv6 */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
check_sips (const char *addr)
|
||||
{
|
||||
/* To keep it simple we only check if the URL starts with "sips:" */
|
||||
return g_str_has_prefix (addr, "sips:");
|
||||
}
|
||||
|
||||
|
||||
const gchar *
|
||||
get_protocol_prefix (const char *protocol)
|
||||
{
|
||||
if (g_strcmp0 (protocol, "UDP") == 0 ||
|
||||
g_strcmp0 (protocol, "TCP") == 0)
|
||||
return "sip";
|
||||
|
||||
if (g_strcmp0 (protocol, "TLS") == 0)
|
||||
return "sips";
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
protocol_is_valid (const char *protocol)
|
||||
{
|
||||
return g_strcmp0 (protocol, "UDP") == 0 ||
|
||||
g_strcmp0 (protocol, "TCP") == 0 ||
|
||||
g_strcmp0 (protocol, "TLS") == 0;
|
||||
}
|
||||
73
plugins/provider/sip/calls-sip-util.h
Normal file
73
plugins/provider/sip/calls-sip-util.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sofia-sip/nua.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
typedef struct {
|
||||
su_home_t home[1];
|
||||
su_root_t *root;
|
||||
} CallsSipContext;
|
||||
|
||||
typedef struct {
|
||||
nua_handle_t *register_handle;
|
||||
nua_handle_t *call_handle;
|
||||
CallsSipContext *context;
|
||||
} CallsSipHandles;
|
||||
|
||||
|
||||
/**
|
||||
* SipEngineState:
|
||||
* @SIP_ENGINE_NULL: Not initialized
|
||||
* @SIP_ENGINE_INITIALIZING: Need to complete initialization
|
||||
* @SIP_ENGINE_ERROR: Unrecoverable/Unhandled sofia-sip error
|
||||
* @SIP_ENGINE_READY: Ready for operation
|
||||
*/
|
||||
typedef enum {
|
||||
SIP_ENGINE_NULL = 0,
|
||||
SIP_ENGINE_INITIALIZING,
|
||||
SIP_ENGINE_ERROR,
|
||||
SIP_ENGINE_READY,
|
||||
} SipEngineState;
|
||||
|
||||
/**
|
||||
* SipMediaEncryption:
|
||||
* @SIP_MEDIA_ENCRYPTION_NONE: Don't encrypt media streams
|
||||
* @SIP_MEDIA_ENCRYPTION_PREFERRED: Prefer using encryption, but also allow unencrypted media
|
||||
* @SIP_MEDIA_ENCRYPTION_FORCED: Force using encryption, drop unencrypted calls
|
||||
*/
|
||||
typedef enum {
|
||||
SIP_MEDIA_ENCRYPTION_NONE = 0,
|
||||
SIP_MEDIA_ENCRYPTION_PREFERRED,
|
||||
SIP_MEDIA_ENCRYPTION_FORCED,
|
||||
} SipMediaEncryption;
|
||||
|
||||
|
||||
gboolean check_sips (const char *addr);
|
||||
gboolean check_ipv6 (const char *host);
|
||||
const char *get_protocol_prefix (const char *protocol);
|
||||
gboolean protocol_is_valid (const char *protocol);
|
||||
771
plugins/provider/sip/calls-srtp-utils.c
Normal file
771
plugins/provider/sip/calls-srtp-utils.c
Normal file
@@ -0,0 +1,771 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-srtp-utils.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <sys/random.h>
|
||||
|
||||
/**
|
||||
* SECTION:srtp-utils
|
||||
* @short_description: SRTP utilities for SDP parsing
|
||||
* @Title: CallsSrtpUtils
|
||||
*
|
||||
* Utilities for parsing and generating the crypto attribute
|
||||
* in SDP for SRTP use based on RFC 4568.
|
||||
*
|
||||
* Note that limitations of libsrtp are taken into account when checking validity
|
||||
* of the parsed attribute. These are:
|
||||
* A maximum of 16 keys,
|
||||
* key derivation rate must be 0,
|
||||
* lifetimes other than 2^48 (we actually ignore the specified lifetimes)
|
||||
*/
|
||||
|
||||
|
||||
/* The default used in libsrtp. No API to change this. See https://github.com/cisco/libsrtp/issues/588 */
|
||||
#define SRTP_DEFAULT_LIFETIME_POW2 48
|
||||
#define SRTP_MAX_LIFETIME_POW2 48
|
||||
|
||||
/* The default used in libsrtp (and GstSrtpEnc/GstSrtpDec) */
|
||||
#define SRTP_DEFAULT_WINDOW_SIZE 128
|
||||
|
||||
const char * srtp_crypto_suites[] = {
|
||||
"AES_CM_128_HMAC_SHA1_32", /* RFC 4568 */
|
||||
"AES_CM_128_HMAC_SHA1_80", /* RFC 4568 */
|
||||
"F8_128_HMAC_SHA1_32", /* RFC 4568 but not supported by GstSrtpEnc/GstSrtpDec */
|
||||
"AEAD_AES_128_GCM", /* RFC 7714 TODO support in the future */
|
||||
"AEAD_AES_256_GCM", /* RFC 7714 TODO support in the future */
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
static gsize
|
||||
get_key_size_for_suite (calls_srtp_crypto_suite suite)
|
||||
{
|
||||
switch (suite) {
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_32:
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_80:
|
||||
return 30;
|
||||
|
||||
case CALLS_SRTP_SUITE_UNKNOWN:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
validate_crypto_attribute (calls_srtp_crypto_attribute *attr,
|
||||
GError **error)
|
||||
{
|
||||
guint expected_key_salt_length;
|
||||
gboolean need_mki;
|
||||
guint expected_mki_length = 0;
|
||||
calls_srtp_crypto_key_param *key_param;
|
||||
GSList *mki_list = NULL; /* for checking uniqueness of MKIs */
|
||||
GSList *key_list = NULL; /* for checking uniqueness of keys */
|
||||
|
||||
if (!attr) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Attribute is NULL");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (attr->tag <= 0 || attr->tag >= 1000000000) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag is not valid: %d", attr->tag);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
switch (attr->crypto_suite) {
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_32:
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_80:
|
||||
expected_key_salt_length = 30; /* 16 byte key + 14 byte salt */
|
||||
break;
|
||||
|
||||
case CALLS_SRTP_SUITE_UNKNOWN:
|
||||
default:
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Crypto suite unknown");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* at least one and a maximum of 16 key parameters */
|
||||
if (attr->n_key_params == 0 || attr->n_key_params > 16) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Must have between 1 and 16 keys, got %d", attr->n_key_params);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
need_mki = attr->n_key_params > 1 ||
|
||||
attr->key_params[0].mki ||
|
||||
attr->key_params[0].mki_length;
|
||||
|
||||
if (need_mki) {
|
||||
expected_mki_length = attr->key_params[0].mki_length;
|
||||
if (expected_mki_length == 0 ||
|
||||
expected_mki_length > 128) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"MKI length must be between 1 and 128, got %u",
|
||||
expected_key_salt_length);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
for (guint i = 0; i < attr->n_key_params; i++) {
|
||||
g_autofree guchar *key_salt = NULL;
|
||||
gsize key_salt_length;
|
||||
|
||||
key_param = &attr->key_params[i];
|
||||
|
||||
/* must have a key */
|
||||
if (!key_param->b64_keysalt) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"No key found in parameter %d", i);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
key_salt = g_base64_decode (key_param->b64_keysalt, &key_salt_length);
|
||||
|
||||
/* key must have length consistent with suite */
|
||||
if (key_salt_length != expected_key_salt_length) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Key %d has length %" G_GSIZE_FORMAT ", but expected %d",
|
||||
i, key_salt_length, expected_key_salt_length);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* key must be unique */
|
||||
if (g_slist_find_custom (key_list,
|
||||
key_param->b64_keysalt,
|
||||
(GCompareFunc) g_strcmp0)) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Key %d is not unique: %s", i, key_param->b64_keysalt);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
key_list = g_slist_append (key_list, key_param->b64_keysalt);
|
||||
|
||||
/* lifetime in range */
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER &&
|
||||
key_param->lifetime >= (1ULL << SRTP_MAX_LIFETIME_POW2)) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Lifetime of key %d out of bounds: Got %" G_GINT64_FORMAT " but maximum is 2^%d",
|
||||
i, key_param->lifetime, SRTP_MAX_LIFETIME_POW2);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO &&
|
||||
key_param->lifetime >= SRTP_MAX_LIFETIME_POW2) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Lifetime of key %d out of bounds: Got 2^%" G_GINT64_FORMAT " but maximum is 2^%d",
|
||||
i, key_param->lifetime, SRTP_MAX_LIFETIME_POW2);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* if MKI length is set, it must be the same for all key parameters */
|
||||
if (need_mki) {
|
||||
if (key_param->mki_length != expected_mki_length) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"MKI length must be the same for all keys. Key %d has length %d but expected %d",
|
||||
i, key_param->mki_length, expected_mki_length);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* MKI must not have leading zero */
|
||||
if (key_param->mki == 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"No MKI set for key %d", i);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* MKI must be unique */
|
||||
if (g_slist_find (mki_list, GINT_TO_POINTER (key_param->mki))) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"MKI for key %d is not unique", i);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
mki_list = g_slist_append (mki_list, GINT_TO_POINTER (key_param->mki));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* check session parameters */
|
||||
|
||||
/* libsrtp does only support kdr=0 */
|
||||
if (attr->kdr != 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Key derivation rate must be 0, got %d",
|
||||
attr->kdr);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
g_slist_free (mki_list);
|
||||
g_slist_free (key_list);
|
||||
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
|
||||
g_slist_free (mki_list);
|
||||
g_slist_free (key_list);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_generate_key_salt:
|
||||
* @length: Desired length of random data
|
||||
*
|
||||
* Generate random data to be used as master key and master salt of desired length @length
|
||||
*
|
||||
* Returns: (transfer full): Random data to be used as key and salt in SRTP
|
||||
* or %NULL if failed. Free with g_free() when done.
|
||||
*/
|
||||
guchar *
|
||||
calls_srtp_generate_key_salt (gsize length)
|
||||
{
|
||||
g_autofree guchar *key_salt = NULL;
|
||||
gsize n_bytes;
|
||||
|
||||
g_return_val_if_fail (length > 0, NULL);
|
||||
|
||||
key_salt = g_malloc (length);
|
||||
|
||||
n_bytes = getrandom (key_salt, length, GRND_NONBLOCK);
|
||||
if (n_bytes == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_steal_pointer (&key_salt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calls_srtp_generate_key_salt_for_suite:
|
||||
* @suite: a #calls_srtp_crypto_suite
|
||||
*
|
||||
* Generate random data to be used as master key and master salt.
|
||||
* The required length is determined by the requirements of the @suite
|
||||
*
|
||||
* Returns: (transfer full): Random data to be used as key and salt in SRTP
|
||||
* or %NULL if failed. Free with g_free() when done.
|
||||
*/
|
||||
guchar *
|
||||
calls_srtp_generate_key_salt_for_suite (calls_srtp_crypto_suite suite)
|
||||
{
|
||||
gsize size = get_key_size_for_suite (suite);
|
||||
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
|
||||
return calls_srtp_generate_key_salt (size);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_parse_sdp_crypto_attribute:
|
||||
* @attribute: attribute line
|
||||
* @error: a #GError
|
||||
*
|
||||
* Parse textual attribute line into structured data.
|
||||
*
|
||||
* Returns: (transfer full): A #calls_srtp_crypto_attribute containing
|
||||
* parsed attribute data, or %NULL if parsing failed.
|
||||
*/
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_srtp_parse_sdp_crypto_attribute (const char *attribute,
|
||||
GError **error)
|
||||
{
|
||||
g_auto (GStrv) attr_fields = NULL;
|
||||
g_auto (GStrv) key_info_strv = NULL;
|
||||
guint n_attr_fields;
|
||||
guint n_key_params;
|
||||
char *tag_str;
|
||||
gint tag;
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
calls_srtp_crypto_suite crypto_suite;
|
||||
gboolean need_mki;
|
||||
gboolean attr_invalid = FALSE;
|
||||
g_autofree char *err_msg = NULL;
|
||||
|
||||
if (STR_IS_NULL_OR_EMPTY (attribute)) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Cannot parse null or empty strings");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!g_str_has_prefix (attribute, "a=crypto:")) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Does not look like a SDP crypto attribute: %s",
|
||||
attribute);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
attr_fields = g_strsplit (attribute, " ", -1);
|
||||
n_attr_fields = g_strv_length (attr_fields);
|
||||
|
||||
if (n_attr_fields < 3) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Need at least three fields in a SDP crypto attribute: %s",
|
||||
attribute);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tag_str = &attr_fields[0][9]; /* 9 is the length of "a=crypto:" */
|
||||
|
||||
/* leading zeros MUST NOT be used */
|
||||
if (*tag_str == '0') {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag must not have a leading zero: %s",
|
||||
tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tag = (int) strtol (tag_str, NULL, 10);
|
||||
if (tag == 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag set to 0: %s", tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tag < 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag must be positive: %s", tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tag >= 1000000000) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag must have a maximum of 9 digits: %s", tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* f.e. attr_fields[1] = "AES_CM_128_HMAC_SHA1_32" */
|
||||
if (g_strcmp0 (attr_fields[1], "AES_CM_128_HMAC_SHA1_32") == 0)
|
||||
crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_32;
|
||||
else if (g_strcmp0 (attr_fields[1], "AES_CM_128_HMAC_SHA1_80") == 0)
|
||||
crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_80;
|
||||
else
|
||||
crypto_suite = CALLS_SRTP_SUITE_UNKNOWN; /* error */
|
||||
|
||||
/* key infos are split by ';' */
|
||||
key_info_strv = g_strsplit (attr_fields[2], ";", -1);
|
||||
n_key_params = g_strv_length (key_info_strv);
|
||||
|
||||
/* libsrtp supports a maximum of 16 master keys */
|
||||
if (n_key_params > 16) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"More than 16 keys are not supported by libsrtp");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
need_mki = n_key_params > 1;
|
||||
|
||||
attr = calls_srtp_crypto_attribute_new (n_key_params);
|
||||
|
||||
attr->tag = tag;
|
||||
attr->crypto_suite = crypto_suite;
|
||||
|
||||
for (guint i = 0; i < n_key_params; i++) {
|
||||
char *key_info; /* srtp-key-info = key-salt ["|" lifetime] ["|" mki] */
|
||||
g_auto (GStrv) key_info_fields = NULL;
|
||||
guint n_key_infos;
|
||||
guint key_info_lifetime_index;
|
||||
guint key_info_mki_index;
|
||||
calls_srtp_crypto_key_param *key_param = &attr->key_params[i];
|
||||
|
||||
if (!g_str_has_prefix (key_info_strv[i], "inline:")) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Key method not 'inline': %s", key_info_strv[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
key_info = &key_info_strv[i][7]; /* 7 is the length of "inline:" */
|
||||
key_info_fields = g_strsplit (key_info, "|", -1);
|
||||
|
||||
n_key_infos = g_strv_length (key_info_fields);
|
||||
|
||||
key_param->b64_keysalt = g_strdup (key_info_fields[0]);
|
||||
|
||||
if (n_key_infos == 1) {
|
||||
key_info_lifetime_index = 0;
|
||||
key_info_mki_index = 0;
|
||||
} else if (n_key_infos == 2) {
|
||||
/* either MKI or lifetime */
|
||||
if (g_strstr_len (key_info_fields[1], -1, ":")) {
|
||||
key_info_lifetime_index = 0;
|
||||
key_info_mki_index = 1;
|
||||
} else {
|
||||
key_info_lifetime_index = 1;
|
||||
key_info_mki_index = 0;
|
||||
}
|
||||
} else if (n_key_infos == 3) {
|
||||
key_info_lifetime_index = 1;
|
||||
key_info_mki_index = 2;
|
||||
} else {
|
||||
/* invalid */
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Unexpected number of key-info fields: %s", key_info);
|
||||
break;
|
||||
}
|
||||
|
||||
/* lifetime type */
|
||||
if (key_info_lifetime_index) {
|
||||
char *lifetime_number;
|
||||
char *endptr;
|
||||
if (g_str_has_prefix (key_info_fields[key_info_lifetime_index], "2^")) {
|
||||
key_param->lifetime_type = CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO;
|
||||
lifetime_number = &key_info_fields[key_info_lifetime_index][2]; /* 2 is the length of "2^" */
|
||||
} else {
|
||||
key_param->lifetime_type = CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER;
|
||||
lifetime_number = key_info_fields[key_info_lifetime_index];
|
||||
}
|
||||
|
||||
if (*lifetime_number == '0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Leading zero in lifetime: %s",
|
||||
key_info_fields[key_info_lifetime_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
key_param->lifetime = g_ascii_strtoull (lifetime_number, &endptr, 10);
|
||||
if (key_param->lifetime == 0) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Lifetime set to zero: %s",
|
||||
key_info_fields[key_info_lifetime_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*endptr != '\0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Non numeric characters in lifetime: %s",
|
||||
key_info_fields[key_info_lifetime_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* out of bounds check will be performed during validation of the attribute */
|
||||
}
|
||||
|
||||
if (need_mki && key_info_mki_index == 0) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI needed, but not found: %s", key_info);
|
||||
break;
|
||||
}
|
||||
|
||||
if (need_mki) {
|
||||
g_auto (GStrv) mki_split = g_strsplit (key_info_fields[key_info_mki_index], ":", -1);
|
||||
guint n_mki = g_strv_length (mki_split);
|
||||
guint64 mki;
|
||||
guint64 mki_length;
|
||||
char *endptr;
|
||||
|
||||
if (n_mki != 2) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI field not separated into two fields by colon: %s",
|
||||
key_info_fields[key_info_mki_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* no leading zero allowed */
|
||||
if (*mki_split[0] == '0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Leading zero in MKI: %s", mki_split[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*mki_split[1] == '0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Leading zero in MKI length: %s", mki_split[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
mki = g_ascii_strtoull (mki_split[0], &endptr, 10);
|
||||
if (mki == 0) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI set to 0: %s", mki_split[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*endptr != '\0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Non numeric characters found in MKI: %s", mki_split[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
mki_length = g_ascii_strtoull (mki_split[1], &endptr, 10);
|
||||
/* number of bytes of the MKI field in the SRTP packet */
|
||||
if (mki_length == 0 || mki_length > 128) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI length not between 0 and 128: %s", mki_split[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*endptr != '\0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Non numeric characters found in MKI length: %s", mki_split[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
key_param->mki = mki;
|
||||
key_param->mki_length = (guint) mki_length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (attr_invalid) {
|
||||
calls_srtp_crypto_attribute_free (attr);
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, err_msg);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO session parameters */
|
||||
|
||||
if (!validate_crypto_attribute (attr, error)) {
|
||||
calls_srtp_crypto_attribute_free (attr);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_print_sdp_crypto_attribute:
|
||||
* @attr: Structured crypto attribute
|
||||
* @error: A #GError
|
||||
*
|
||||
* Returns: (transfer full): Textual representation of crypto attribute
|
||||
* or %NULL if attribute contains invalid data.
|
||||
*/
|
||||
char *
|
||||
calls_srtp_print_sdp_crypto_attribute (calls_srtp_crypto_attribute *attr,
|
||||
GError **error)
|
||||
{
|
||||
const char *crypto_suite;
|
||||
GString *attr_str;
|
||||
|
||||
if (!validate_crypto_attribute (attr, error))
|
||||
return NULL;
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_32)
|
||||
crypto_suite = "AES_CM_128_HMAC_SHA1_32";
|
||||
else if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_80)
|
||||
crypto_suite = "AES_CM_128_HMAC_SHA1_80";
|
||||
else
|
||||
return NULL;
|
||||
|
||||
attr_str = g_string_sized_new (96); /* minimal string length is 82 */
|
||||
|
||||
g_string_append_printf (attr_str, "a=crypto:%d %s ",
|
||||
attr->tag, crypto_suite);
|
||||
|
||||
/* key parameters */
|
||||
for (guint i = 0; i < attr->n_key_params; i++) {
|
||||
calls_srtp_crypto_key_param *key_param = &attr->key_params[i];
|
||||
|
||||
g_string_append_printf (attr_str, "inline:%s", key_param->b64_keysalt);
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER)
|
||||
g_string_append_printf (attr_str, "|%" G_GINT64_FORMAT, key_param->lifetime);
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO)
|
||||
g_string_append_printf (attr_str, "|2^%" G_GINT64_FORMAT, key_param->lifetime);
|
||||
|
||||
if (key_param->mki > 0) {
|
||||
g_string_append_printf(attr_str, "|%" G_GUINT64_FORMAT ":%u",
|
||||
key_param->mki, key_param->mki_length);
|
||||
}
|
||||
|
||||
if (i + 1 < attr->n_key_params)
|
||||
g_string_append (attr_str, ";");
|
||||
}
|
||||
|
||||
/* TODO session parameters */
|
||||
|
||||
return g_string_free (attr_str, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_attribute_new:
|
||||
* @n_key_params: The number of key parameters
|
||||
*
|
||||
* Returns: (transfer full): A new empty #calls_srtp_crypto_attribute
|
||||
* with @n_key_params key parameters allocated. Key parameters must be set either
|
||||
* manually or by using calls_srtp_crypto_attribute_init_keys().
|
||||
*
|
||||
* Free the attribute with calls_srtp_crypto_attribute_free() after you are done.
|
||||
*/
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_srtp_crypto_attribute_new (guint n_key_params)
|
||||
{
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
g_return_val_if_fail (n_key_params > 0 || n_key_params < 16, NULL);
|
||||
|
||||
attr = g_new0 (calls_srtp_crypto_attribute, 1);
|
||||
attr->key_params = g_new0 (calls_srtp_crypto_key_param, n_key_params);
|
||||
attr->n_key_params = n_key_params;
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_attribute_init_keys:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
*
|
||||
* Generate key material and set sane default parameters for each
|
||||
* key parameter.
|
||||
*/
|
||||
gboolean
|
||||
calls_srtp_crypto_attribute_init_keys (calls_srtp_crypto_attribute *attr)
|
||||
{
|
||||
gsize key_size;
|
||||
gboolean need_mki;
|
||||
|
||||
g_return_val_if_fail (attr, FALSE);
|
||||
|
||||
key_size = get_key_size_for_suite (attr->crypto_suite);
|
||||
if (key_size == 0)
|
||||
return FALSE;
|
||||
|
||||
need_mki = attr->n_key_params > 1;
|
||||
|
||||
for (uint i = 0; i < attr->n_key_params; i++) {
|
||||
g_autofree guchar *key = calls_srtp_generate_key_salt (key_size);
|
||||
g_free (attr->key_params[i].b64_keysalt);
|
||||
attr->key_params[i].b64_keysalt = g_base64_encode (key, key_size);
|
||||
|
||||
if (need_mki) {
|
||||
attr->key_params[i].mki = i + 1;
|
||||
attr->key_params[i].mki_length = 4;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_attribute_free:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
*
|
||||
* Frees all memory allocated for @attr.
|
||||
*/
|
||||
void
|
||||
calls_srtp_crypto_attribute_free (calls_srtp_crypto_attribute *attr)
|
||||
{
|
||||
for (guint i = 0; i < attr->n_key_params; i++) {
|
||||
g_free (attr->key_params[i].b64_keysalt);
|
||||
}
|
||||
|
||||
g_free (attr->key_params);
|
||||
g_free (attr->b64_fec_key);
|
||||
g_free (attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_get_srtpdec_params:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
* @srtp_cipher (out): SRTP cipher
|
||||
* @srtp_auth (out): SRTP auth
|
||||
* @srtcp_cipher (out): SRTCP cipher
|
||||
* @srtcp_auth (out): SRTCP auth
|
||||
*
|
||||
* Sets the parameters suitable for #GstSrtpDec (as a #GstCaps).
|
||||
*/
|
||||
gboolean
|
||||
calls_srtp_crypto_get_srtpdec_params (calls_srtp_crypto_attribute *attr,
|
||||
const char **srtp_cipher,
|
||||
const char **srtp_auth,
|
||||
const char **srtcp_cipher,
|
||||
const char **srtcp_auth)
|
||||
{
|
||||
g_return_val_if_fail (attr, FALSE);
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_32) {
|
||||
*srtp_cipher = attr->unencrypted_srtp ? "null" : "aes-128-icm";
|
||||
*srtp_auth = attr->unauthenticated_srtp ? "null" : "hmac-sha1-32";
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? "null" : "aes-128-icm";
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? "null" : "hmac-sha1-32";
|
||||
|
||||
return TRUE;
|
||||
} else if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_80) {
|
||||
*srtp_cipher = attr->unencrypted_srtp ? "null" : "aes-128-icm";
|
||||
*srtp_auth = attr->unauthenticated_srtp ? "null" : "hmac-sha1-80";
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? "null" : "aes-128-icm";
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? "null" : "hmac-sha1-80";
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_get_srtpenc_params:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
* @srtp_cipher (out): SRTP cipher
|
||||
* @srtp_auth (out): SRTP auth
|
||||
* @srtcp_cipher (out): SRTCP cipher
|
||||
* @srtcp_auth (out): SRTCP auth
|
||||
*
|
||||
* Sets the parameters suitable for #GstSrtpDec (as a #GstCaps).
|
||||
*/
|
||||
gboolean
|
||||
calls_srtp_crypto_get_srtpenc_params (calls_srtp_crypto_attribute *attr,
|
||||
GstSrtpCipherType *srtp_cipher,
|
||||
GstSrtpAuthType *srtp_auth,
|
||||
GstSrtpCipherType *srtcp_cipher,
|
||||
GstSrtpAuthType *srtcp_auth)
|
||||
{
|
||||
g_return_val_if_fail (attr, FALSE);
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_32) {
|
||||
*srtp_cipher = attr->unencrypted_srtp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtp_auth = attr->unauthenticated_srtp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_32;
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_32;
|
||||
|
||||
return TRUE;
|
||||
} else if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_80) {
|
||||
|
||||
*srtp_cipher = attr->unencrypted_srtp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtp_auth = attr->unauthenticated_srtp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_80;
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_80;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
133
plugins/provider/sip/calls-srtp-utils.h
Normal file
133
plugins/provider/sip/calls-srtp-utils.h
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
CALLS_SRTP_SUITE_UNKNOWN = 0,
|
||||
CALLS_SRTP_SUITE_AES_128_SHA1_32,
|
||||
CALLS_SRTP_SUITE_AES_128_SHA1_80,
|
||||
} calls_srtp_crypto_suite;
|
||||
|
||||
|
||||
typedef enum {
|
||||
CALLS_SRTP_FEC_ORDER_UNSET = 0,
|
||||
CALLS_SRTP_FEC_ORDER_FEC_SRTP,
|
||||
CALLS_SRTP_FEC_ORDER_SRTP_FEC
|
||||
} calls_srtp_fec_order;
|
||||
|
||||
|
||||
typedef enum {
|
||||
CALLS_SRTP_LIFETIME_UNSET = 0,
|
||||
CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER,
|
||||
CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO
|
||||
} calls_srtp_lifetime_type;
|
||||
|
||||
|
||||
/* from GStreamer */
|
||||
typedef enum {
|
||||
GST_SRTP_CIPHER_NULL,
|
||||
GST_SRTP_CIPHER_AES_128_ICM,
|
||||
GST_SRTP_CIPHER_AES_256_ICM,
|
||||
GST_SRTP_CIPHER_AES_128_GCM,
|
||||
GST_SRTP_CIPHER_AES_256_GCM
|
||||
} GstSrtpCipherType;
|
||||
|
||||
|
||||
typedef enum {
|
||||
GST_SRTP_AUTH_NULL,
|
||||
GST_SRTP_AUTH_HMAC_SHA1_32,
|
||||
GST_SRTP_AUTH_HMAC_SHA1_80
|
||||
} GstSrtpAuthType;
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *b64_keysalt;
|
||||
calls_srtp_lifetime_type lifetime_type;
|
||||
/* maximum lifetime: SRTP 2^48 packets; SRTCP 2^31 packets; libsrtp uses a hardcoded limit of 2^48 */
|
||||
guint64 lifetime;
|
||||
|
||||
guint64 mki;
|
||||
guint mki_length;
|
||||
} calls_srtp_crypto_key_param;
|
||||
|
||||
|
||||
typedef struct {
|
||||
gint tag;
|
||||
calls_srtp_crypto_suite crypto_suite;
|
||||
|
||||
calls_srtp_crypto_key_param *key_params;
|
||||
guint n_key_params;
|
||||
|
||||
/** session parameters
|
||||
* For more information see https://datatracker.ietf.org/doc/html/rfc4568#section-6.3
|
||||
* KDR (key derivation rate) defaults to 0; declarative parameter
|
||||
* decimal integer in {1,2,...,24}, denotes a power of two. 0 means unspecified
|
||||
* defaulting to a single initial key derivation
|
||||
* UNENCRYPTED_SRTCP, UNENCRYPTED_SRTP; negotiated parameter
|
||||
* UNAUTHENTICATED_SRTP; negotiated parameter
|
||||
* FEC_ORDER; declarative parameter
|
||||
* FEC_KEY separate key-params for forward error correction; declarative parameter
|
||||
* WSH (Window Size Hint); declarative parameter; MAY be ignored
|
||||
*/
|
||||
gint kdr;
|
||||
gboolean unencrypted_srtp;
|
||||
gboolean unencrypted_srtcp;
|
||||
gboolean unauthenticated_srtp;
|
||||
calls_srtp_fec_order fec_order; /* FEC in RTP: RFC2733 */
|
||||
char *b64_fec_key; /* TODO this should also be an array of calls_srtp_crypto_key_param */
|
||||
guint window_size_hint;
|
||||
} calls_srtp_crypto_attribute;
|
||||
|
||||
|
||||
guchar *calls_srtp_generate_key_salt (gsize length);
|
||||
guchar *calls_srtp_generate_key_salt_for_suite (calls_srtp_crypto_suite suite);
|
||||
calls_srtp_crypto_attribute *calls_srtp_parse_sdp_crypto_attribute (const char *attr,
|
||||
GError **error);
|
||||
char *calls_srtp_print_sdp_crypto_attribute (calls_srtp_crypto_attribute *attr,
|
||||
GError **error);
|
||||
calls_srtp_crypto_attribute *calls_srtp_crypto_attribute_new (guint n_key_params);
|
||||
gboolean calls_srtp_crypto_attribute_init_keys (calls_srtp_crypto_attribute *attr);
|
||||
void calls_srtp_crypto_attribute_free (calls_srtp_crypto_attribute *attr);
|
||||
char *calls_srtp_generate_crypto_for_offer (void);
|
||||
gboolean calls_srtp_crypto_get_srtpdec_params (calls_srtp_crypto_attribute *attr,
|
||||
const char **srtp_cipher,
|
||||
const char **srtp_auth,
|
||||
const char **srtcp_cipher,
|
||||
const char **srtcp_auth);
|
||||
gboolean calls_srtp_crypto_get_srtpenc_params (calls_srtp_crypto_attribute *attr,
|
||||
GstSrtpCipherType *srtp_cipher,
|
||||
GstSrtpAuthType *srtp_auth,
|
||||
GstSrtpCipherType *srtcp_cipher,
|
||||
GstSrtpAuthType *srtcp_auth);
|
||||
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (calls_srtp_crypto_attribute, calls_srtp_crypto_attribute_free)
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
144
plugins/provider/sip/gst-rfc3551.c
Normal file
144
plugins/provider/sip/gst-rfc3551.c
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 "CallsGstRfc3551"
|
||||
|
||||
#include "gst-rfc3551.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
/* Use the following codecs in order of preference */
|
||||
static MediaCodecInfo gst_codecs[] = {
|
||||
{8, "PCMA", 8000, 1, "rtppcmapay", "rtppcmadepay", "alawenc", "alawdec", "libgstalaw.so"},
|
||||
{0, "PCMU", 8000, 1, "rtppcmupay", "rtppcmudepay", "mulawenc", "mulawdec", "libgstmulaw.so"},
|
||||
{3, "GSM", 8000, 1, "rtpgsmpay", "rtpgsmdepay", "gsmenc", "gsmdec", "libgstgsm.so"},
|
||||
/* {9, "G722", 8000, 1, "rtpg722pay", "rtpg722depay", "avenc_g722", "avdec_g722", "libgstlibav.so"}, */
|
||||
{4, "G723", 8000, 1, "rtpg723pay", "rtpg723depay", "avenc_g723_1", "avdec_g723_1", "libgstlibav.so"}, // does not seem to work
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* media_codec_available_in_gst:
|
||||
* @codec: A #MediaCodecInfo
|
||||
*
|
||||
* Returns: %TRUE if codec is available on your system, %FALSE otherwise
|
||||
*/
|
||||
gboolean
|
||||
media_codec_available_in_gst (MediaCodecInfo *codec)
|
||||
{
|
||||
gboolean available = FALSE;
|
||||
GstRegistry *registry = gst_registry_get ();
|
||||
GstPlugin *plugin = NULL;
|
||||
|
||||
plugin = gst_registry_lookup (registry, codec->filename);
|
||||
available = !!plugin;
|
||||
|
||||
if (plugin)
|
||||
gst_object_unref (plugin);
|
||||
|
||||
g_debug ("Gstreamer plugin for %s %s available",
|
||||
codec->name, available ? "is" : "is not");
|
||||
return available;
|
||||
}
|
||||
|
||||
/* media_codec_by_name:
|
||||
*
|
||||
* @name: The name of the codec
|
||||
*
|
||||
* Returns: (transfer none): A #MediaCodecInfo, if found.
|
||||
* You should check if the codec is available on your system before
|
||||
* trying to use it with media_codec_available_in_gst()
|
||||
*/
|
||||
MediaCodecInfo *
|
||||
media_codec_by_name (const char *name)
|
||||
{
|
||||
g_return_val_if_fail (name, NULL);
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (gst_codecs); i++) {
|
||||
if (g_strcmp0 (name, gst_codecs[i].name) == 0)
|
||||
return &gst_codecs[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* media_codec_by_payload_id:
|
||||
*
|
||||
* @payload_id: The payload id (see RFC 3551, 3555, 4733, 4855)
|
||||
*
|
||||
* Returns: (transfer none): A #MediaCodecInfo, if found.
|
||||
* You should check if the codec is available on your system before
|
||||
* trying to use it with media_codec_available_in_gst()
|
||||
*/
|
||||
MediaCodecInfo *
|
||||
media_codec_by_payload_id (guint payload_id)
|
||||
{
|
||||
for (guint i = 0; i < G_N_ELEMENTS (gst_codecs); i++) {
|
||||
if (payload_id == gst_codecs[i].payload_id)
|
||||
return &gst_codecs[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* media_codec_get_gst_capabilities:
|
||||
*
|
||||
* @codec: A #MediaCodecInfo
|
||||
* @use_srtp: Whether to use SRTP
|
||||
*
|
||||
* Returns: (transfer full): The capability string describing GstCaps.
|
||||
* Used for the RTP source element.
|
||||
*/
|
||||
gchar *
|
||||
media_codec_get_gst_capabilities (MediaCodecInfo *codec,
|
||||
gboolean use_srtp)
|
||||
{
|
||||
return g_strdup_printf ("application/%s,media=(string)audio,clock-rate=(int)%u"
|
||||
",encoding-name=(string)%s,payload=(int)%u",
|
||||
use_srtp ? "x-srtp" : "x-rtp",
|
||||
codec->clock_rate,
|
||||
codec->name,
|
||||
codec->payload_id);
|
||||
}
|
||||
|
||||
/* media_codecs_get_candidates:
|
||||
*
|
||||
* Returns: (transfer container): A #GList of codec candidates of type #MediaCodecInfo.
|
||||
* Free the list with g_list_free when done.
|
||||
*/
|
||||
GList *
|
||||
media_codecs_get_candidates (void)
|
||||
{
|
||||
GList *candidates = NULL;
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (gst_codecs); i++) {
|
||||
if (media_codec_available_in_gst (&gst_codecs[i])) {
|
||||
g_debug ("Adding %s to the codec candidates", gst_codecs[i].name);
|
||||
candidates = g_list_append (candidates, &gst_codecs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
52
plugins/provider/sip/gst-rfc3551.h
Normal file
52
plugins/provider/sip/gst-rfc3551.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* For more information
|
||||
* see: https://tools.ietf.org/html/rfc3551#section-6
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
guint payload_id;
|
||||
char *name;
|
||||
gint clock_rate;
|
||||
gint channels;
|
||||
char *gst_payloader_name;
|
||||
char *gst_depayloader_name;
|
||||
char *gst_encoder_name;
|
||||
char *gst_decoder_name;
|
||||
char *filename;
|
||||
} MediaCodecInfo;
|
||||
|
||||
|
||||
gboolean media_codec_available_in_gst (MediaCodecInfo *codec);
|
||||
MediaCodecInfo *media_codec_by_name (const char *name);
|
||||
MediaCodecInfo *media_codec_by_payload_id (uint payload_id);
|
||||
gchar *media_codec_get_gst_capabilities (MediaCodecInfo *codec,
|
||||
gboolean use_srtp);
|
||||
GList *media_codecs_get_candidates (void);
|
||||
96
plugins/provider/sip/meson.build
Normal file
96
plugins/provider/sip/meson.build
Normal file
@@ -0,0 +1,96 @@
|
||||
#
|
||||
# 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
|
||||
#
|
||||
|
||||
sip_include = include_directories('.')
|
||||
|
||||
sip_install_dir = join_paths(full_calls_plugin_libdir, 'sip')
|
||||
|
||||
sip_plugin = configure_file(
|
||||
input: 'sip.plugin.in',
|
||||
output: 'sip.plugin',
|
||||
configuration: config_data,
|
||||
install_dir: sip_install_dir
|
||||
)
|
||||
|
||||
sip_deps = [
|
||||
dependency('gobject-2.0'),
|
||||
dependency('gstreamer-1.0'),
|
||||
dependency('gtk+-3.0'),
|
||||
dependency('libhandy-1', version: '>= 1.1.90'),
|
||||
dependency('libpeas-1.0'),
|
||||
dependency('sofia-sip-ua-glib'),
|
||||
dependency('libsecret-1'),
|
||||
]
|
||||
|
||||
sip_sources = files(
|
||||
[
|
||||
'calls-sdp-crypto-context.c', 'calls-sdp-crypto-context.h',
|
||||
'calls-sip-call.c', 'calls-sip-call.h',
|
||||
'calls-sip-origin.c', 'calls-sip-origin.h',
|
||||
'calls-sip-provider.c', 'calls-sip-provider.h',
|
||||
'calls-sip-util.c', 'calls-sip-util.h',
|
||||
'calls-sip-media-manager.c', 'calls-sip-media-manager.h',
|
||||
'calls-sip-media-pipeline.c', 'calls-sip-media-pipeline.h',
|
||||
'calls-sip-account-widget.c', 'calls-sip-account-widget.h',
|
||||
'calls-srtp-utils.c', 'calls-srtp-utils.h',
|
||||
'gst-rfc3551.c', 'gst-rfc3551.h',
|
||||
]
|
||||
)
|
||||
|
||||
pipeline_enum_headers = [
|
||||
'calls-sip-media-pipeline.h',
|
||||
]
|
||||
|
||||
pipeline_enums = gnome.mkenums_simple('calls-media-pipeline-enums',
|
||||
sources: pipeline_enum_headers)
|
||||
sip_sources += pipeline_enums
|
||||
|
||||
sip_enum_headers = [
|
||||
'calls-sdp-crypto-context.h',
|
||||
'calls-sip-util.h',
|
||||
]
|
||||
|
||||
sip_enums = gnome.mkenums_simple('calls-sip-enums',
|
||||
sources: sip_enum_headers,
|
||||
)
|
||||
|
||||
sip_sources += sip_enums
|
||||
|
||||
sip_resources = gnome.compile_resources(
|
||||
'sip-resources',
|
||||
'sip.gresources.xml',
|
||||
source_dir: '.',
|
||||
c_name: 'call',
|
||||
)
|
||||
|
||||
sip_sources += sip_resources
|
||||
|
||||
calls_sip = shared_module(
|
||||
'sip',
|
||||
sip_sources,
|
||||
dependencies: sip_deps,
|
||||
include_directories: src_include,
|
||||
link_with: libcalls,
|
||||
install: true,
|
||||
install_dir: sip_install_dir
|
||||
)
|
||||
239
plugins/provider/sip/sip-account-widget.ui
Normal file
239
plugins/provider/sip/sip-account-widget.ui
Normal file
@@ -0,0 +1,239 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="1.0"/>
|
||||
<template class="CallsSipAccountWidget" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header_add">
|
||||
<property name="title" translatable="yes">Add Account</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="login_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="label" translatable="yes">_Log In</property>
|
||||
<signal name="clicked" handler="on_login_clicked" swapped="yes"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner_add">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header_edit">
|
||||
<property name="visible">True</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<property name="title" translatable="yes">Manage Account</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="label" translatable="yes">_Apply</property>
|
||||
<signal name="clicked" handler="on_apply_clicked" swapped="yes"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="delete_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="label" translatable="yes">_Delete</property>
|
||||
<signal name="clicked" handler="on_delete_clicked" swapped="yes"/>
|
||||
<style>
|
||||
<class name="destructive-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner_edit">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="HdyPreferencesPage">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Server</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="host">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Display Name</property>
|
||||
<property name="subtitle" translatable="yes">Optional</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="display_name">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">User ID</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="user">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Password</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="password">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="input-purpose">password</property>
|
||||
<property name="visibility">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<property name="secondary_icon_activatable">True</property>
|
||||
<property name="secondary_icon_name">view-reveal-symbolic</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
<signal name="icon-press" handler="on_password_visibility_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Port</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="port">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
<signal name="insert-text" handler="on_port_entry_insert_text" swapped="yes"/>
|
||||
<signal name="insert-text" handler="on_port_entry_after_insert_text" swapped="yes" after="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyComboRow" id="protocol">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Transport</property>
|
||||
<signal name="notify::selected-index" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyComboRow" id="media_encryption">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Media Encryption</property>
|
||||
<signal name="notify::selected-index" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Use for Phone Calls</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="tel_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="notify::active" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Automatically Connect</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="auto_connect_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="notify::active" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
<object class="GtkSizeGroup">
|
||||
<widgets>
|
||||
<widget name="host"/>
|
||||
<widget name="display_name"/>
|
||||
<widget name="user"/>
|
||||
<widget name="password"/>
|
||||
<widget name="port"/>
|
||||
</widgets>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
6
plugins/provider/sip/sip.gresources.xml
Normal file
6
plugins/provider/sip/sip.gresources.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/gnome/Calls/ui">
|
||||
<file preprocess="xml-stripblanks">sip-account-widget.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
7
plugins/provider/sip/sip.plugin.in
Normal file
7
plugins/provider/sip/sip.plugin.in
Normal file
@@ -0,0 +1,7 @@
|
||||
[Plugin]
|
||||
Module=sip
|
||||
Name=SIP
|
||||
Description=SIP calls provider
|
||||
Authors=Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
Copyright=Copyright (C) 2021 Purism SPC
|
||||
Website=@PACKAGE_URL_RAW@
|
||||
Reference in New Issue
Block a user