All checks were successful
CI Code / Check spelling (push) Successful in 18s
CI Code / Check coding style (push) Successful in 34s
CI Code / Code Coverage (push) Successful in 2m36s
CI Code / Linux (debian) (push) Successful in 4m41s
CI Code / Linux (ubuntu) (push) Successful in 4m52s
CI Code / Linux (arch) (push) Successful in 5m40s
Sync with upstream profanity-im/profanity.
Major upstream changes incorporated:
Memory management
- Replace malloc+memset with g_new0 throughout codebase
- Adopt auto_gchar / auto_gcharv / auto_gerror / auto_jid cleanup macros
- Replace free() with g_free() for GAlloc'd memory
Editor rewrite
- Remove pthread-based async editor; use GChildWatch callback API
- New launch_editor(initial_content, callback, user_data) interface
- Proper signal handling (SIGINT, SIGTSTP, SIGPIPE reset in child)
- ui_suspend()/ui_resume() integration for TTY management
OMEMO improvements
- Dual backend support: libsignal-protocol-c and libomemo-c
- Proper pre-key removal after use (XEP-0384 compliance)
- Automatic pre-key regeneration when store drops below threshold
- New functions: omemo_is_device_active(), omemo_is_jid_trusted()
- omemo_get_jid_untrusted_fingerprints() for better error messages
- Fingerprint notifications on new device identity discovery
- Deterministic pre-key ID generation tracking max_pre_key_id
- omemo_trust_changed() UI updates on trust state changes
JID validation
- RFC 6122-compliant validation in jid_is_valid()
- Character-level checks (RFC 6122 forbidden chars: & ' / : < > @)
- Length limits: 1023 per component, 3071 total
- New jid_is_valid_user_jid() for user vs. service JID distinction
Database
- Schema migration v3: UNIQUE constraint on archive_id for deduplication
- Triggers for corrected message tracking (replaces_db_id / replaced_by_db_id)
- db_history_result_t return type, _truncate_datetime_suffix()
UI / console
- win_warn_needed() / win_warn_sent() warning deduplication hash table
- PAD_MIN_HEIGHT dynamic pad sizing with PAD_THRESHOLD auto-cleanup
- Spellcheck integration in input field with Unicode word detection
- cons_spellcheck_setting() for /settings ui output
- /[command]? shortcut for command help
Account config
- Account name sanitization for GKeyFile special chars ([ ] = # \n \r)
- Replace popen() with g_spawn_sync() for eval_password
- TLS policy: add "direct" option alongside legacy
Connection
- Port validation with g_assert (0–65535)
- SHA-256 certificate fingerprint support (XMPP_CERT_PUBKEY_FINGERPRINT_SHA256)
- "direct" TLS policy alias for legacy SSL
Common utilities
- str_xml_sanitize() for XML 1.0 illegal character removal
- string_matches_one_of() with formatted error messages
- valid_tls_policy_option() helper
- prof_date_time_format_iso8601() utility
- Improved strip_arg_quotes() with backslash unescaping
- prof_occurrences() uses g_slist_prepend + reverse for performance
PGP / OX
- Proper GPGME resource cleanup with goto-cleanup pattern
- g_string_free(xmppuri) leak fix in _ox_key_lookup
CSV export
- Use GString + g_file_set_contents instead of raw write() syscalls
Tests
- Restructured into subdirectories: command/, config/, xmpp/, ui/, omemo/, otr/, pgp/
- New test_cmd_ac.c for autocompleter unit tests
- Updated stubs for new UI suspend/resume functions
License headers
- Migrate to SPDX-3.0 identifiers (GPL-3.0-or-later WITH OpenSSL-exception)
────────────────────────────────────────────────────
cproof-specific preservations:
- XEP-0308 LMC: replace_id ?: id logic in message/stanza/omemo
- Force encryption: cmd_force_encryption, test_forced_encryption
- CWE-134: format string protection (cons_show("%s", ...))
- y_start_pos-based paging in window.c
- db_history_result_t return type, _truncate_datetime_suffix()
Merge-time fixes:
- common.c: format-security (-Werror) — cons_show(errmsg) → cons_show("%s", errmsg)
- database.c: null-deref guard — !msg->timestamp → msg && !msg->timestamp
- console.c: implicit size_t → int cast — (int)(maxlen + 1)
- tlscerts.c: %d for size_t — %zu
Build system:
- Kept autotools (Makefile.am, configure.ac); upstream uses Meson
- Restored deleted files: bootstrap.sh, autogen.sh, ax_valgrind_check.m4, configure-debug
- Updated Makefile.am test paths for subdirectory structure
- Added test_cmd_ac, test_forced_encryption to test sources
Functional tests:
- Use cproof version; upstream requires stbbr_for_xmlns from updated stabber
- Not yet available in devs/stabber fork
Closes #64
Merge author: jabber.developer2
Commits authors:
Michael Vetter <jubalh@iodoru.org>
& Steffen Jaeckel <s@jaeckel.eu>
301 lines
9.0 KiB
C
301 lines
9.0 KiB
C
/*
|
|
* otrlibv4.c
|
|
* vim: expandtab:ts=4:sts=4:sw=4
|
|
*
|
|
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <libotr/proto.h>
|
|
#include <libotr/privkey.h>
|
|
#include <libotr/message.h>
|
|
|
|
#include "log.h"
|
|
#include "otr/otr.h"
|
|
#include "otr/otrlib.h"
|
|
#include "ui/ui.h"
|
|
#include "ui/window_list.h"
|
|
|
|
static GTimer* timer;
|
|
static unsigned int current_interval;
|
|
|
|
OtrlPolicy
|
|
otrlib_policy(void)
|
|
{
|
|
return OTRL_POLICY_ALLOW_V1 | OTRL_POLICY_ALLOW_V2;
|
|
}
|
|
|
|
void
|
|
otrlib_init_timer(void)
|
|
{
|
|
OtrlUserState user_state = otr_userstate();
|
|
timer = g_timer_new();
|
|
current_interval = otrl_message_poll_get_default_interval(user_state);
|
|
}
|
|
|
|
void
|
|
otrlib_shutdown(void)
|
|
{
|
|
g_timer_destroy(timer);
|
|
}
|
|
|
|
void
|
|
otrlib_poll(void)
|
|
{
|
|
gdouble elapsed = g_timer_elapsed(timer, NULL);
|
|
|
|
if (current_interval != 0 && elapsed > current_interval) {
|
|
OtrlUserState user_state = otr_userstate();
|
|
OtrlMessageAppOps* ops = otr_messageops();
|
|
otrl_message_poll(user_state, ops, NULL);
|
|
g_timer_start(timer);
|
|
}
|
|
}
|
|
|
|
char*
|
|
otrlib_start_query(void)
|
|
{
|
|
return "?OTR?v2? This user has requested an Off-the-Record private conversation. However, you do not have a plugin to support that. See http://otr.cypherpunks.ca/ for more information.";
|
|
}
|
|
|
|
static const char*
|
|
cb_otr_error_message(void* opdata, ConnContext* context, OtrlErrorCode err_code)
|
|
{
|
|
switch (err_code) {
|
|
case OTRL_ERRCODE_ENCRYPTION_ERROR:
|
|
return strdup("OTR Error: occurred while encrypting a message");
|
|
case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE:
|
|
return strdup("OTR Error: Sent encrypted message to somebody who is not in a mutual OTR session");
|
|
case OTRL_ERRCODE_MSG_UNREADABLE:
|
|
return strdup("OTR Error: sent an unreadable encrypted message");
|
|
case OTRL_ERRCODE_MSG_MALFORMED:
|
|
return strdup("OTR Error: message sent is malformed");
|
|
default:
|
|
return strdup("OTR Error: unknown");
|
|
}
|
|
}
|
|
|
|
static void
|
|
cb_otr_error_message_free(void* opdata, const char* err_msg)
|
|
{
|
|
free((char*)err_msg);
|
|
}
|
|
|
|
static void
|
|
cb_timer_control(void* opdata, unsigned int interval)
|
|
{
|
|
current_interval = interval;
|
|
}
|
|
|
|
static void
|
|
cb_handle_msg_event(void* opdata, OtrlMessageEvent msg_event,
|
|
ConnContext* context, const char* message,
|
|
gcry_error_t err)
|
|
{
|
|
GString* err_msg;
|
|
switch (msg_event) {
|
|
case OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
|
|
ui_handle_otr_error(context->username, "OTR: Policy requires encryption, but attempting to send an unencrypted message.");
|
|
break;
|
|
case OTRL_MSGEVENT_ENCRYPTION_ERROR:
|
|
ui_handle_otr_error(context->username, "OTR: Error occurred while encrypting a message, message not sent.");
|
|
break;
|
|
case OTRL_MSGEVENT_CONNECTION_ENDED:
|
|
ui_handle_otr_error(context->username, "OTR: Message not sent because contact has ended the private conversation.");
|
|
break;
|
|
case OTRL_MSGEVENT_SETUP_ERROR:
|
|
ui_handle_otr_error(context->username, "OTR: A private conversation could not be set up.");
|
|
break;
|
|
case OTRL_MSGEVENT_MSG_REFLECTED:
|
|
ui_handle_otr_error(context->username, "OTR: Received our own OTR message.");
|
|
break;
|
|
case OTRL_MSGEVENT_MSG_RESENT:
|
|
ui_handle_otr_error(context->username, "OTR: The previous message was resent.");
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
|
|
ui_handle_otr_error(context->username, "OTR: Received an encrypted message but no private connection established.");
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
|
|
ui_handle_otr_error(context->username, "OTR: Cannot read the received message.");
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_MALFORMED:
|
|
ui_handle_otr_error(context->username, "OTR: The message received contains malformed data.");
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
|
|
err_msg = g_string_new("OTR: Received error: ");
|
|
g_string_append(err_msg, message);
|
|
g_string_append(err_msg, ".");
|
|
ui_handle_otr_error(context->username, err_msg->str);
|
|
g_string_free(err_msg, TRUE);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
|
|
err_msg = g_string_new("OTR: Received an unencrypted message: ");
|
|
g_string_append(err_msg, message);
|
|
ui_handle_otr_error(context->username, err_msg->str);
|
|
g_string_free(err_msg, TRUE);
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
|
|
ui_handle_otr_error(context->username, "OTR: Cannot recognize the type of message received.");
|
|
break;
|
|
case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
|
|
ui_handle_otr_error(context->username, "OTR: Received and discarded a message intended for another instance.");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
cb_handle_smp_event(void* opdata, OtrlSMPEvent smp_event,
|
|
ConnContext* context, unsigned short progress_percent,
|
|
char* question)
|
|
{
|
|
NextExpectedSMP nextMsg = context->smstate->nextExpected;
|
|
OtrlUserState user_state = otr_userstate();
|
|
OtrlMessageAppOps* ops = otr_messageops();
|
|
GHashTable* smp_initiators = otr_smpinitators();
|
|
|
|
ProfChatWin* chatwin = wins_get_chat(context->username);
|
|
|
|
switch (smp_event) {
|
|
case OTRL_SMPEVENT_ASK_FOR_SECRET:
|
|
if (chatwin) {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_INIT, NULL);
|
|
}
|
|
g_hash_table_insert(smp_initiators, strdup(context->username), strdup(context->username));
|
|
break;
|
|
|
|
case OTRL_SMPEVENT_ASK_FOR_ANSWER:
|
|
if (chatwin) {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_INIT_Q, question);
|
|
}
|
|
break;
|
|
|
|
case OTRL_SMPEVENT_SUCCESS:
|
|
if (chatwin) {
|
|
if (context->smstate->received_question == 0) {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_SUCCESS, NULL);
|
|
chatwin_otr_trust(chatwin);
|
|
} else {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_SUCCESS_Q, NULL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OTRL_SMPEVENT_FAILURE:
|
|
if (chatwin) {
|
|
if (context->smstate->received_question == 0) {
|
|
if (nextMsg == OTRL_SMP_EXPECT3) {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_SENDER_FAIL, NULL);
|
|
} else if (nextMsg == OTRL_SMP_EXPECT4) {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_RECEIVER_FAIL, NULL);
|
|
}
|
|
chatwin_otr_untrust(chatwin);
|
|
} else {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_FAIL_Q, NULL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OTRL_SMPEVENT_ERROR:
|
|
otrl_message_abort_smp(user_state, ops, NULL, context);
|
|
break;
|
|
|
|
case OTRL_SMPEVENT_CHEATED:
|
|
otrl_message_abort_smp(user_state, ops, NULL, context);
|
|
break;
|
|
|
|
case OTRL_SMPEVENT_ABORT:
|
|
if (chatwin) {
|
|
chatwin_otr_smp_event(chatwin, PROF_OTR_SMP_ABORT, NULL);
|
|
chatwin_otr_untrust(chatwin);
|
|
}
|
|
break;
|
|
|
|
case OTRL_SMPEVENT_IN_PROGRESS:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
otrlib_init_ops(OtrlMessageAppOps* ops)
|
|
{
|
|
ops->otr_error_message = cb_otr_error_message;
|
|
ops->otr_error_message_free = cb_otr_error_message_free;
|
|
ops->handle_msg_event = cb_handle_msg_event;
|
|
ops->handle_smp_event = cb_handle_smp_event;
|
|
ops->timer_control = cb_timer_control;
|
|
}
|
|
|
|
ConnContext*
|
|
otrlib_context_find(OtrlUserState user_state, const char* const recipient, char* jid)
|
|
{
|
|
return otrl_context_find(user_state, recipient, jid, "xmpp", OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
|
|
}
|
|
|
|
void
|
|
otrlib_end_session(OtrlUserState user_state, const char* const recipient, char* jid, OtrlMessageAppOps* ops)
|
|
{
|
|
ConnContext* context = otrl_context_find(user_state, recipient, jid, "xmpp",
|
|
OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
|
|
|
|
if (context) {
|
|
otrl_message_disconnect(user_state, ops, NULL, jid, "xmpp", recipient, 0);
|
|
}
|
|
}
|
|
|
|
gcry_error_t
|
|
otrlib_encrypt_message(OtrlUserState user_state, OtrlMessageAppOps* ops, char* jid, const char* const to,
|
|
const char* const message, char** newmessage)
|
|
{
|
|
gcry_error_t err;
|
|
|
|
err = otrl_message_sending(
|
|
user_state,
|
|
ops,
|
|
NULL,
|
|
jid,
|
|
"xmpp",
|
|
to,
|
|
OTRL_INSTAG_MASTER,
|
|
message,
|
|
0,
|
|
newmessage,
|
|
OTRL_FRAGMENT_SEND_SKIP,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
int
|
|
otrlib_decrypt_message(OtrlUserState user_state, OtrlMessageAppOps* ops, char* jid, const char* const from,
|
|
const char* const message, char** decrypted, OtrlTLV** tlvs)
|
|
{
|
|
return otrl_message_receiving(
|
|
user_state,
|
|
ops,
|
|
NULL,
|
|
jid,
|
|
"xmpp",
|
|
from,
|
|
message,
|
|
decrypted,
|
|
tlvs,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
otrlib_handle_tlvs(OtrlUserState user_state, OtrlMessageAppOps* ops, ConnContext* context, OtrlTLV* tlvs, GHashTable* smp_initiators)
|
|
{
|
|
}
|