irssi/src/fe-common/irc/dcc/fe-dcc-chat.c
moirhira b61f546588 fe-dcc-chat: fix segfault in dcc_closed() when window already destroyed
During shutdown, query_destroy() fires before dcc_close(), leaving
the DCC record pointing at a freed window. dcc_closed() then calls
printformat() with NULL as the window item, which causes a NULL
dereference in store_lineinfo_tmp() at textbuffer-formats.c:148.

Fix by looking up the query window and passing its name string
to printformat(), falling back to NULL which safely prints to
the server window. Also removes dead code where sender was
allocated but never used in the printformat call.

Reproducer: /dcc chat <nick>, then /quit
Tested against both a local server and irc.libera.chat.
2026-04-27 16:00:50 +01:00

390 lines
11 KiB
C

/*
fe-dcc-chat.c : irssi
Copyright (C) 1999-2002 Timo Sirainen
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "module.h"
#include <irssi/src/core/signals.h>
#include <irssi/src/core/commands.h>
#include <irssi/src/core/levels.h>
#include <irssi/src/core/settings.h>
#include <irssi/src/core/misc.h>
#include <irssi/src/irc/core/irc.h>
#include <irssi/src/irc/core/irc-servers.h>
#include <irssi/src/irc/core/irc-queries.h>
#include <irssi/src/irc/dcc/dcc-chat.h>
#include <irssi/src/fe-common/irc/dcc/module-formats.h>
#include <irssi/src/fe-common/core/printtext.h>
#include <irssi/src/fe-common/core/fe-messages.h>
#include <irssi/src/fe-common/core/chat-completion.h>
void fe_dcc_chat_messages_init(void);
void fe_dcc_chat_messages_deinit(void);
static void dcc_request(CHAT_DCC_REC *dcc)
{
if (!IS_DCC_CHAT(dcc)) return;
printformat(dcc->server, NULL, MSGLEVEL_DCC,
server_ischannel(SERVER(dcc->server), dcc->target) ? IRCTXT_DCC_CHAT_CHANNEL :
IRCTXT_DCC_CHAT, dcc->id, dcc->addrstr,
dcc->port, dcc->target);
}
static void dcc_connected(CHAT_DCC_REC *dcc)
{
char *sender;
if (!IS_DCC_CHAT(dcc)) return;
sender = g_strconcat("=", dcc->id, NULL);
printformat(dcc->server, NULL, MSGLEVEL_DCC,
IRCTXT_DCC_CHAT_CONNECTED,
dcc->id, dcc->addrstr, dcc->port);
if (query_find(NULL, sender) == NULL) {
int level = settings_get_level("autocreate_query_level");
int autocreate_dccquery = (level & MSGLEVEL_DCCMSGS) != 0;
if (!autocreate_dccquery)
completion_last_message_add(sender);
else
irc_query_create(dcc->servertag, sender, TRUE);
}
g_free(sender);
}
static void dcc_closed(CHAT_DCC_REC *dcc)
{
QUERY_REC *query;
char *sender;
if (!IS_DCC_CHAT(dcc)) return;
sender = g_strconcat("=", dcc->id, NULL);
query = query_find((SERVER_REC *)dcc->server, sender);
g_free(sender);
printformat(dcc->server, query ? query->name : NULL, MSGLEVEL_DCC,
IRCTXT_DCC_CHAT_DISCONNECTED, dcc->id);
}
static void dcc_chat_msg(CHAT_DCC_REC *dcc, const char *msg)
{
QUERY_REC *query;
char *sender, *freemsg;
g_return_if_fail(IS_DCC_CHAT(dcc));
g_return_if_fail(msg != NULL);
sender = g_strconcat("=", dcc->id, NULL);
query = query_find(NULL, sender);
if (settings_get_bool("emphasis"))
msg = freemsg = expand_emphasis((WI_ITEM_REC *) query, msg);
else
freemsg = NULL;
if (query == NULL)
completion_last_message_add(sender);
signal_emit("message dcc", 2, dcc, msg);
g_free_not_null(freemsg);
g_free(sender);
}
static void dcc_chat_action(CHAT_DCC_REC *dcc, const char *msg)
{
char *sender;
g_return_if_fail(IS_DCC_CHAT(dcc));
g_return_if_fail(msg != NULL);
sender = g_strconcat("=", dcc->id, NULL);
if (query_find(NULL, sender) == NULL)
completion_last_message_add(sender);
signal_emit("message dcc action", 2, dcc, msg);
g_free(sender);
}
static void dcc_chat_ctcp(CHAT_DCC_REC *dcc, const char *cmd, const char *data)
{
g_return_if_fail(IS_DCC_CHAT(dcc));
signal_emit("message dcc ctcp", 3, dcc, cmd, data);
}
static void dcc_error_ctcp(const char *type, const char *data,
const char *nick, const char *addr,
const char *target)
{
printformat(NULL, NULL, MSGLEVEL_DCC,
IRCTXT_DCC_INVALID_CTCP, type, nick, addr, target);
}
static void dcc_unknown_ctcp(IRC_SERVER_REC *server, const char *data,
const char *nick, const char *addr,
const char *target, CHAT_DCC_REC *chat)
{
char *type, *args;
void *free_arg;
g_return_if_fail(data != NULL);
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
&type, &args))
return;
printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_CTCP,
type, nick, args);
cmd_params_free(free_arg);
}
static void dcc_unknown_reply(IRC_SERVER_REC *server, const char *data,
const char *nick)
{
char *type, *args;
void *free_arg;
g_return_if_fail(data != NULL);
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
&type, &args))
return;
printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_UNKNOWN_REPLY,
type, nick, args);
cmd_params_free(free_arg);
}
static void sig_dcc_destroyed(CHAT_DCC_REC *dcc)
{
QUERY_REC *query;
char *nick;
if (!IS_DCC_CHAT(dcc)) return;
nick = g_strconcat("=", dcc->id, NULL);
query = query_find(NULL, nick);
if (query != NULL) {
/* DCC chat closed, close the query with it. */
if (dcc->connection_lost) query->unwanted = TRUE;
query_destroy(query);
} else {
/* remove nick from msg completion
since it won't work anymore */
completion_last_message_remove(nick);
}
g_free(nick);
}
static void sig_query_destroyed(QUERY_REC *query)
{
CHAT_DCC_REC *dcc;
if (*query->name != '=')
return;
dcc = dcc_chat_find_id(query->name+1);
if (dcc != NULL && !dcc->destroyed) {
/* DCC query window closed, close the dcc chat too. */
dcc_close(DCC(dcc));
}
}
static void dcc_error_close_not_found(const char *type, const char *nick,
const char *fname)
{
g_return_if_fail(type != NULL);
g_return_if_fail(nick != NULL);
if (g_ascii_strcasecmp(type, "CHAT") != 0) return;
printformat(NULL, NULL, MSGLEVEL_DCC,
IRCTXT_DCC_CHAT_NOT_FOUND, nick);
}
static void sig_dcc_list_print(CHAT_DCC_REC *dcc)
{
if (!IS_DCC_CHAT(dcc)) return;
printformat(NULL, NULL, MSGLEVEL_DCC, IRCTXT_DCC_LIST_LINE_CHAT,
dcc->id, "CHAT");
}
static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
CHAT_DCC_REC *dcc;
GHashTable *optlist;
char *text, *target;
void *free_arg;
g_return_if_fail(data != NULL);
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_UNKNOWN_OPTIONS |
PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, "msg",
&optlist, &target, &text))
return;
/* handle only DCC messages */
if (g_strcmp0(target, "*") == 0)
dcc = item_get_dcc(item);
else if (*target == '=')
dcc = dcc_chat_find_id(target+1);
else
dcc = NULL;
if (dcc == NULL && *target == '=') {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
IRCTXT_DCC_CHAT_NOT_FOUND, target+1);
} else if (dcc != NULL) {
if (query_find(NULL, target) == NULL)
completion_last_message_add(target);
signal_emit("message dcc own", 2, dcc, text);
}
cmd_params_free(free_arg);
}
static void cmd_me(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
CHAT_DCC_REC *dcc;
dcc = item_get_dcc(item);
if (dcc != NULL)
signal_emit("message dcc own_action", 2, dcc, data);
}
static void cmd_action(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
{
CHAT_DCC_REC *dcc;
char *target, *text;
void *free_arg;
g_return_if_fail(data != NULL);
if (*data != '=') {
/* handle only DCC actions */
return;
}
if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST,
&target, &text))
return;
if (*target == '\0' || *text == '\0')
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
dcc = dcc_chat_find_id(target+1);
if (dcc == NULL || dcc->sendbuf == NULL) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
IRCTXT_DCC_CHAT_NOT_FOUND, target+1);
} else {
if (query_find(NULL, target) == NULL)
completion_last_message_add(target);
signal_emit("message dcc own_action", 2, dcc, text);
}
cmd_params_free(free_arg);
}
static void cmd_ctcp(const char *data, SERVER_REC *server)
{
CHAT_DCC_REC *dcc;
char *target, *ctcpcmd, *ctcpdata;
void *free_arg;
g_return_if_fail(data != NULL);
if (server == NULL || !server->connected)
cmd_return_error(CMDERR_NOT_CONNECTED);
if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST,
&target, &ctcpcmd, &ctcpdata))
return;
if (*target == '\0' || *ctcpcmd == '\0')
cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
if (*target != '=') {
/* handle only DCC CTCPs */
cmd_params_free(free_arg);
return;
}
dcc = dcc_chat_find_id(target+1);
if (dcc == NULL || dcc->sendbuf == NULL) {
printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
IRCTXT_DCC_CHAT_NOT_FOUND, target+1);
} else {
ascii_strup(ctcpcmd);
signal_emit("message dcc own_ctcp", 3, dcc, ctcpcmd, ctcpdata);
}
cmd_params_free(free_arg);
}
void fe_dcc_chat_init(void)
{
fe_dcc_chat_messages_init();
signal_add("dcc request", (SIGNAL_FUNC) dcc_request);
signal_add("dcc connected", (SIGNAL_FUNC) dcc_connected);
signal_add("dcc closed", (SIGNAL_FUNC) dcc_closed);
signal_add("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg);
signal_add("dcc ctcp action", (SIGNAL_FUNC) dcc_chat_action);
signal_add("default dcc ctcp", (SIGNAL_FUNC) dcc_chat_ctcp);
signal_add("dcc error ctcp", (SIGNAL_FUNC) dcc_error_ctcp);
signal_add("default ctcp msg dcc", (SIGNAL_FUNC) dcc_unknown_ctcp);
signal_add("default ctcp reply dcc", (SIGNAL_FUNC) dcc_unknown_reply);
signal_add("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
signal_add("query destroyed", (SIGNAL_FUNC) sig_query_destroyed);
signal_add("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
command_bind("me", NULL, (SIGNAL_FUNC) cmd_me);
command_bind("action", NULL, (SIGNAL_FUNC) cmd_action);
command_bind("ctcp", NULL, (SIGNAL_FUNC) cmd_ctcp);
signal_add("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
}
void fe_dcc_chat_deinit(void)
{
fe_dcc_chat_messages_deinit();
signal_remove("dcc request", (SIGNAL_FUNC) dcc_request);
signal_remove("dcc connected", (SIGNAL_FUNC) dcc_connected);
signal_remove("dcc closed", (SIGNAL_FUNC) dcc_closed);
signal_remove("dcc chat message", (SIGNAL_FUNC) dcc_chat_msg);
signal_remove("dcc ctcp action", (SIGNAL_FUNC) dcc_chat_action);
signal_remove("default dcc ctcp", (SIGNAL_FUNC) dcc_chat_ctcp);
signal_remove("dcc error ctcp", (SIGNAL_FUNC) dcc_error_ctcp);
signal_remove("default ctcp msg dcc", (SIGNAL_FUNC) dcc_unknown_ctcp);
signal_remove("default ctcp reply dcc", (SIGNAL_FUNC) dcc_unknown_reply);
signal_remove("dcc destroyed", (SIGNAL_FUNC) sig_dcc_destroyed);
signal_remove("query destroyed", (SIGNAL_FUNC) sig_query_destroyed);
signal_remove("dcc list print", (SIGNAL_FUNC) sig_dcc_list_print);
command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
command_unbind("me", (SIGNAL_FUNC) cmd_me);
command_unbind("action", (SIGNAL_FUNC) cmd_action);
command_unbind("ctcp", (SIGNAL_FUNC) cmd_ctcp);
signal_remove("dcc error close not found", (SIGNAL_FUNC) dcc_error_close_not_found);
}