Files
cproof/src/otr/otrlibv4.c
Jabber Developer 72f4f186da
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
merge: sync upstream profanity-im/profanity
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>
2026-05-26 17:48:14 +00:00

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)
{
}