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:
Evangelos Ribeiro Tzaras
2022-07-16 22:16:24 +02:00
committed by Guido Günther
parent 8c6ece6a87
commit 86a8f3ae22
60 changed files with 19 additions and 13 deletions

View 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);
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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 */

View 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

View 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;
}

View 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);

View 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;
}

View 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

View 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;
}

View 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);

View 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
)

View 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>

View 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>

View 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@