Files
profanity/src/xmpp/session.c
Steffen Jaeckel 7a2af5e3ad fix handling of connection errors
When a `see-other-host` stream-error is received we try to re-connect to
the other host. Erroneously this also started the `reconnect_timer`.
This lead to the behavior that in cases where e.g. the login failed
we try to reconnect instead of bailing out with an error.

This commit fixes the wrong behavior by not starting the `reconnect_timer`.

Fix 0e58509c16

Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
2022-02-08 19:52:13 +01:00

607 lines
18 KiB
C

/*
* session.c
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
*
* This file is part of Profanity.
*
* Profanity 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.
*
* Profanity 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 Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
*
*/
#include "config.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include "profanity.h"
#include "log.h"
#include "common.h"
#include "config/preferences.h"
#include "plugins/plugins.h"
#include "event/server_events.h"
#include "event/client_events.h"
#include "xmpp/bookmark.h"
#include "xmpp/blocking.h"
#include "xmpp/connection.h"
#include "xmpp/capabilities.h"
#include "xmpp/session.h"
#include "xmpp/iq.h"
#include "xmpp/message.h"
#include "xmpp/presence.h"
#include "xmpp/roster.h"
#include "xmpp/stanza.h"
#include "xmpp/xmpp.h"
#include "xmpp/muc.h"
#include "xmpp/chat_session.h"
#include "xmpp/jid.h"
#ifdef HAVE_OMEMO
#include "omemo/omemo.h"
#include "xmpp/omemo.h"
#endif
// for auto reconnect
static struct
{
char* name;
char* passwd;
} saved_account;
static struct
{
char* name;
char* jid;
char* passwd;
char* altdomain;
int port;
char* tls_policy;
char* auth_policy;
} saved_details;
typedef enum {
ACTIVITY_ST_ACTIVE,
ACTIVITY_ST_IDLE,
ACTIVITY_ST_AWAY,
ACTIVITY_ST_XA,
} activity_state_t;
static GTimer* reconnect_timer;
static activity_state_t activity_state;
static resource_presence_t saved_presence;
static char* saved_status;
static void _session_reconnect(void);
static void _session_free_internals(void);
static void _session_free_saved_details(void);
void
session_init(void)
{
log_info("Initialising XMPP");
connection_init();
presence_sub_requests_init();
caps_init();
}
jabber_conn_status_t
session_connect_with_account(const ProfAccount* const account)
{
assert(account != NULL);
log_info("Connecting using account: %s", account->name);
_session_free_internals();
// save account name and password for reconnect
saved_account.name = strdup(account->name);
saved_account.passwd = strdup(account->password);
char* jid = NULL;
if (account->resource) {
Jid* jidp = jid_create_from_bare_and_resource(account->jid, account->resource);
jid = strdup(jidp->fulljid);
jid_destroy(jidp);
} else {
jid = strdup(account->jid);
}
jabber_conn_status_t result = connection_connect(
jid,
account->password,
account->server,
account->port,
account->tls_policy,
account->auth_policy);
free(jid);
return result;
}
jabber_conn_status_t
session_connect_with_details(const char* const jid, const char* const passwd, const char* const altdomain,
const int port, const char* const tls_policy, const char* const auth_policy)
{
assert(jid != NULL);
assert(passwd != NULL);
_session_free_internals();
// save details for reconnect, remember name for account creating on success
saved_details.name = strdup(jid);
saved_details.passwd = strdup(passwd);
if (altdomain) {
saved_details.altdomain = strdup(altdomain);
} else {
saved_details.altdomain = NULL;
}
if (port != 0) {
saved_details.port = port;
} else {
saved_details.port = 0;
}
if (tls_policy) {
saved_details.tls_policy = strdup(tls_policy);
} else {
saved_details.tls_policy = NULL;
}
if (auth_policy) {
saved_details.auth_policy = strdup(auth_policy);
} else {
saved_details.auth_policy = NULL;
}
// use 'profanity' when no resourcepart in provided jid
Jid* jidp = jid_create(jid);
if (jidp->resourcepart == NULL) {
jid_destroy(jidp);
char* resource = jid_random_resource();
jidp = jid_create_from_bare_and_resource(jid, resource);
free(resource);
saved_details.jid = strdup(jidp->fulljid);
} else {
saved_details.jid = strdup(jid);
}
jid_destroy(jidp);
// connect with fulljid
log_info("Connecting without account, JID: %s", saved_details.jid);
return connection_connect(
saved_details.jid,
passwd,
saved_details.altdomain,
saved_details.port,
saved_details.tls_policy,
saved_details.auth_policy);
}
void
session_autoping_fail(void)
{
session_lost_connection();
}
void
session_disconnect(void)
{
// if connected, send end stream and wait for response
if (connection_get_status() == JABBER_CONNECTED) {
log_info("Closing connection");
char* account_name = session_get_account_name();
const char* fulljid = connection_get_fulljid();
plugins_on_disconnect(account_name, fulljid);
accounts_set_last_activity(session_get_account_name());
iq_rooms_cache_clear();
iq_handlers_clear();
connection_disconnect();
message_handlers_clear();
connection_clear_data();
chat_sessions_clear();
presence_clear_sub_requests();
}
connection_set_disconnected();
}
void
session_shutdown(void)
{
_session_free_internals();
chat_sessions_clear();
presence_clear_sub_requests();
connection_shutdown();
if (saved_status) {
free(saved_status);
}
}
void
session_process_events(void)
{
int reconnect_sec;
jabber_conn_status_t conn_status = connection_get_status();
switch (conn_status) {
case JABBER_CONNECTED:
case JABBER_CONNECTING:
case JABBER_RAW_CONNECTED:
case JABBER_RAW_CONNECTING:
case JABBER_DISCONNECTING:
connection_check_events();
break;
case JABBER_DISCONNECTED:
reconnect_sec = prefs_get_reconnect();
if ((reconnect_sec != 0) && reconnect_timer) {
int elapsed_sec = g_timer_elapsed(reconnect_timer, NULL);
if (elapsed_sec > reconnect_sec) {
_session_reconnect();
}
}
break;
case JABBER_RECONNECT:
_session_reconnect();
break;
default:
break;
}
}
char*
session_get_account_name(void)
{
return saved_account.name;
}
static int
_receive_mood(xmpp_stanza_t* const stanza, void* const userdata)
{
const char* from = xmpp_stanza_get_from(stanza);
xmpp_stanza_t* event = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_EVENT, STANZA_NS_PUBSUB_EVENT);
if (event) {
xmpp_stanza_t* items = xmpp_stanza_get_child_by_name(event, STANZA_NAME_ITEMS);
if (items) {
xmpp_stanza_t* item = xmpp_stanza_get_child_by_name(items, STANZA_NAME_ITEM);
if (item) {
xmpp_stanza_t* mood = xmpp_stanza_get_child_by_name_and_ns(item, STANZA_NAME_MOOD, STANZA_NS_MOOD);
if (mood) {
xmpp_stanza_t* c = xmpp_stanza_get_children(mood);
if (c) {
const char* m = xmpp_stanza_get_name(c);
xmpp_stanza_t* t = xmpp_stanza_get_child_by_name(mood, STANZA_NAME_TEXT);
if (t) {
const char* text = xmpp_stanza_get_text(t);
cons_show("Mood from %s %s (%s)", from, m, text);
} else {
cons_show("Mood from %s %s", from, m);
}
}
}
}
}
}
return TRUE;
}
void
session_login_success(gboolean secured)
{
chat_sessions_init();
message_handlers_init();
presence_handlers_init();
iq_handlers_init();
// logged in with account
if (saved_account.name) {
log_debug("Connection handler: logged in with account name: %s", saved_account.name);
sv_ev_login_account_success(saved_account.name, secured);
// logged in without account, use details to create new account
} else {
log_debug("Connection handler: logged in with jid: %s", saved_details.name);
accounts_add(saved_details.name, saved_details.altdomain, saved_details.port, saved_details.tls_policy, saved_details.auth_policy);
accounts_set_jid(saved_details.name, saved_details.jid);
saved_account.name = strdup(saved_details.name);
saved_account.passwd = strdup(saved_details.passwd);
_session_free_saved_details();
sv_ev_login_account_success(saved_account.name, secured);
}
roster_request();
bookmark_request();
blocking_request();
// items discovery
connection_request_features();
char* domain = connection_get_domain();
iq_disco_items_request_onconnect(domain);
if (prefs_get_boolean(PREF_CARBONS)) {
iq_enable_carbons();
}
if ((prefs_get_reconnect() != 0) && reconnect_timer) {
g_timer_destroy(reconnect_timer);
reconnect_timer = NULL;
}
message_pubsub_event_handler_add(STANZA_NS_MOOD, _receive_mood, NULL, NULL);
caps_add_feature(STANZA_NS_MOOD_NOTIFY);
}
void
session_login_failed(void)
{
if (reconnect_timer == NULL) {
log_debug("Connection handler: No reconnect timer");
sv_ev_failed_login();
_session_free_internals();
} else {
log_debug("Connection handler: Restarting reconnect timer");
if (prefs_get_reconnect() != 0) {
g_timer_start(reconnect_timer);
}
}
connection_clear_data();
chat_sessions_clear();
presence_clear_sub_requests();
}
void
session_lost_connection(void)
{
/* this callback also clears all cached data */
sv_ev_lost_connection();
if (prefs_get_reconnect() != 0) {
assert(reconnect_timer == NULL);
reconnect_timer = g_timer_new();
} else {
_session_free_internals();
}
}
void
session_init_activity(void)
{
activity_state = ACTIVITY_ST_ACTIVE;
saved_status = NULL;
}
void
session_check_autoaway(void)
{
jabber_conn_status_t conn_status = connection_get_status();
if (conn_status != JABBER_CONNECTED) {
return;
}
char* mode = prefs_get_string(PREF_AUTOAWAY_MODE);
gboolean check = prefs_get_boolean(PREF_AUTOAWAY_CHECK);
gint away_time = prefs_get_autoaway_time();
gint xa_time = prefs_get_autoxa_time();
int away_time_ms = away_time * 60000;
int xa_time_ms = xa_time * 60000;
char* account = session_get_account_name();
resource_presence_t curr_presence = accounts_get_last_presence(account);
char* curr_status = accounts_get_last_status(account);
unsigned long idle_ms = ui_get_idle_time();
switch (activity_state) {
case ACTIVITY_ST_ACTIVE:
if (idle_ms >= away_time_ms) {
if (g_strcmp0(mode, "away") == 0) {
if ((curr_presence == RESOURCE_ONLINE) || (curr_presence == RESOURCE_CHAT) || (curr_presence == RESOURCE_DND)) {
activity_state = ACTIVITY_ST_AWAY;
// save current presence
saved_presence = curr_presence;
if (saved_status) {
free(saved_status);
}
if (curr_status) {
saved_status = strdup(curr_status);
} else {
saved_status = NULL;
}
// send away presence with last activity
char* message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
connection_set_presence_msg(message);
if (prefs_get_boolean(PREF_LASTACTIVITY)) {
cl_ev_presence_send(RESOURCE_AWAY, idle_ms / 1000);
} else {
cl_ev_presence_send(RESOURCE_AWAY, 0);
}
int pri = accounts_get_priority_for_presence_type(account, RESOURCE_AWAY);
if (message) {
cons_show("Idle for %d minutes, status set to away (priority %d), \"%s\".", away_time, pri, message);
} else {
cons_show("Idle for %d minutes, status set to away (priority %d).", away_time, pri);
}
g_free(message);
title_bar_set_presence(CONTACT_AWAY);
}
} else if (g_strcmp0(mode, "idle") == 0) {
activity_state = ACTIVITY_ST_IDLE;
// send current presence with last activity
connection_set_presence_msg(curr_status);
cl_ev_presence_send(curr_presence, idle_ms / 1000);
}
}
break;
case ACTIVITY_ST_IDLE:
if (check && (idle_ms < away_time_ms)) {
activity_state = ACTIVITY_ST_ACTIVE;
cons_show("No longer idle.");
// send current presence without last activity
connection_set_presence_msg(curr_status);
cl_ev_presence_send(curr_presence, 0);
}
break;
case ACTIVITY_ST_AWAY:
if (xa_time_ms > 0 && (idle_ms >= xa_time_ms)) {
activity_state = ACTIVITY_ST_XA;
// send extended away presence with last activity
char* message = prefs_get_string(PREF_AUTOXA_MESSAGE);
connection_set_presence_msg(message);
if (prefs_get_boolean(PREF_LASTACTIVITY)) {
cl_ev_presence_send(RESOURCE_XA, idle_ms / 1000);
} else {
cl_ev_presence_send(RESOURCE_XA, 0);
}
int pri = accounts_get_priority_for_presence_type(account, RESOURCE_XA);
if (message) {
cons_show("Idle for %d minutes, status set to xa (priority %d), \"%s\".", xa_time, pri, message);
} else {
cons_show("Idle for %d minutes, status set to xa (priority %d).", xa_time, pri);
}
g_free(message);
title_bar_set_presence(CONTACT_XA);
} else if (check && (idle_ms < away_time_ms)) {
activity_state = ACTIVITY_ST_ACTIVE;
cons_show("No longer idle.");
// send saved presence without last activity
connection_set_presence_msg(saved_status);
cl_ev_presence_send(saved_presence, 0);
contact_presence_t contact_pres = contact_presence_from_resource_presence(saved_presence);
title_bar_set_presence(contact_pres);
}
break;
case ACTIVITY_ST_XA:
if (check && (idle_ms < away_time_ms)) {
activity_state = ACTIVITY_ST_ACTIVE;
cons_show("No longer idle.");
// send saved presence without last activity
connection_set_presence_msg(saved_status);
cl_ev_presence_send(saved_presence, 0);
contact_presence_t contact_pres = contact_presence_from_resource_presence(saved_presence);
title_bar_set_presence(contact_pres);
}
break;
}
free(curr_status);
g_free(mode);
}
static struct
{
gchar* altdomain;
unsigned short altport;
} reconnect;
/* This takes ownership of `altdomain`, i.e. the caller must not
* free the value after calling this function.
*/
void
session_reconnect(gchar* altdomain, unsigned short altport)
{
reconnect.altdomain = altdomain;
reconnect.altport = altport;
}
static void
_session_reconnect(void)
{
// reconnect with account.
ProfAccount* account = accounts_get_account(saved_account.name);
if (account == NULL) {
log_error("Unable to reconnect, account no longer exists: %s", saved_account.name);
return;
}
char* jid = NULL;
if (account->resource) {
jid = create_fulljid(account->jid, account->resource);
} else {
jid = strdup(account->jid);
}
const char* server;
unsigned short port;
if (reconnect.altdomain) {
server = reconnect.altdomain;
port = reconnect.altport;
} else {
server = account->server;
port = account->port;
}
log_debug("Attempting reconnect with account %s", account->name);
connection_connect(jid, saved_account.passwd, server, port, account->tls_policy, account->auth_policy);
free(jid);
account_free(account);
if (reconnect_timer)
g_timer_start(reconnect_timer);
}
static void
_session_free_internals(void)
{
FREE_SET_NULL(saved_account.name);
FREE_SET_NULL(saved_account.passwd);
GFREE_SET_NULL(reconnect.altdomain);
_session_free_saved_details();
}
static void
_session_free_saved_details(void)
{
FREE_SET_NULL(saved_details.name);
FREE_SET_NULL(saved_details.jid);
FREE_SET_NULL(saved_details.passwd);
FREE_SET_NULL(saved_details.altdomain);
FREE_SET_NULL(saved_details.tls_policy);
FREE_SET_NULL(saved_details.auth_policy);
}