Coverage Report

Created: 2026-04-12 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/irssi/src/core/chat-commands.c
Line
Count
Source
1
/*
2
 chat-commands.c : irssi
3
4
    Copyright (C) 2000 Timo Sirainen
5
6
    This program is free software; you can redistribute it and/or modify
7
    it under the terms of the GNU General Public License as published by
8
    the Free Software Foundation; either version 2 of the License, or
9
    (at your option) any later version.
10
11
    This program is distributed in the hope that it will be useful,
12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
    GNU General Public License for more details.
15
16
    You should have received a copy of the GNU General Public License along
17
    with this program; if not, write to the Free Software Foundation, Inc.,
18
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
*/
20
21
#include "module.h"
22
#include <irssi/src/core/network.h>
23
#include <irssi/src/core/signals.h>
24
#include <irssi/src/core/commands.h>
25
#include <irssi/src/core/special-vars.h>
26
#include <irssi/src/core/settings.h>
27
28
#include <irssi/src/core/chat-protocols.h>
29
#include <irssi/src/core/servers.h>
30
#include <irssi/src/core/servers-setup.h>
31
#include <irssi/src/core/servers-reconnect.h>
32
#include <irssi/src/core/channels.h>
33
#include <irssi/src/core/chatnets.h>
34
#include <irssi/src/core/queries.h>
35
#include <irssi/src/core/window-item-def.h>
36
#include <irssi/src/core/rawlog.h>
37
38
static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr,
39
                char **rawlog_file)
40
0
{
41
0
        CHAT_PROTOCOL_REC *proto;
42
0
  SERVER_CONNECT_REC *conn;
43
0
  GHashTable *optlist;
44
0
  char *addr, *portstr, *password, *nick, *chatnet, *host;
45
0
  void *free_arg;
46
47
0
  g_return_val_if_fail(data != NULL, NULL);
48
49
0
  if (!cmd_get_params(data, &free_arg, 4 | PARAM_FLAG_OPTIONS,
50
0
          "connect", &optlist, &addr, &portstr,
51
0
          &password, &nick))
52
0
    return NULL;
53
0
  if (plus_addr != NULL) *plus_addr = *addr == '+';
54
0
  if (*addr == '+') addr++;
55
0
  if (*addr == '\0') {
56
0
    signal_emit("error command", 1,
57
0
          GINT_TO_POINTER(CMDERR_NOT_ENOUGH_PARAMS));
58
0
    cmd_params_free(free_arg);
59
0
    return NULL;
60
0
  }
61
62
0
  if (g_strcmp0(password, "-") == 0)
63
0
    *password = '\0';
64
65
        /* check if -<chatnet> option is used to specify chat protocol */
66
0
  proto = chat_protocol_find_net(optlist);
67
68
  /* connect to server */
69
0
  chatnet = proto == NULL ? NULL :
70
0
    g_hash_table_lookup(optlist, proto->chatnet);
71
72
0
  if (chatnet == NULL)
73
0
    chatnet = g_hash_table_lookup(optlist, "network");
74
75
0
  conn = server_create_conn_opt(proto != NULL ? proto->id : -1, addr, atoi(portstr), chatnet,
76
0
                                password, nick, optlist);
77
0
  if (conn == NULL) {
78
0
    signal_emit("error command", 1,
79
0
          GINT_TO_POINTER(CMDERR_NO_SERVER_DEFINED));
80
0
    cmd_params_free(free_arg);
81
0
    return NULL;
82
0
  }
83
84
0
  if (proto == NULL)
85
0
    proto = chat_protocol_find_id(conn->chat_type);
86
87
0
  if (proto->not_initialized) {
88
    /* trying to use protocol that isn't yet initialized */
89
0
    signal_emit("chat protocol unknown", 1, proto->name);
90
0
    server_connect_unref(conn);
91
0
                cmd_params_free(free_arg);
92
0
    return NULL;
93
0
  }
94
95
0
  if (strchr(addr, '/') != NULL && chatnet_find(addr) == NULL)
96
0
    conn->unix_socket = TRUE;
97
98
  /* TLS options are handled in server_create_conn_opt ... -> server_setup_fill_optlist */
99
100
0
  *rawlog_file = g_strdup(g_hash_table_lookup(optlist, "rawlog"));
101
102
0
        host = g_hash_table_lookup(optlist, "host");
103
0
  if (host != NULL && *host != '\0') {
104
0
    IPADDR ip4 = { 0 };
105
0
    IPADDR ip6 = { 0 };
106
0
    if (net_gethostbyname_first_ips(host, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT, &ip4,
107
0
                                    &ip6) == 0)
108
0
      server_connect_own_ip_save(conn, &ip4, &ip6);
109
0
  }
110
111
0
  cmd_params_free(free_arg);
112
0
        return conn;
113
0
}
114
115
/* SYNTAX: CONNECT [-4 | -6] [-tls_cert <cert>] [-tls_pkey <pkey>] [-tls_pass <password>]
116
                   [-tls_verify] [-tls_cafile <cafile>] [-tls_capath <capath>]
117
                   [-tls_ciphers <list>] [-tls_pinned_cert <fingerprint>]
118
                   [-tls_pinned_pubkey <fingerprint>] [-!] [-noautosendcmd] [-tls | -notls]
119
                   [-nocap] [-starttls | -disallow_starttls] [-noproxy]
120
                   [-network <network>] [-host <hostname>] [-rawlog <file>]
121
                   <address>|<chatnet> [<port> [<password> [<nick>]]] */
122
/* NOTE: -network replaces the old -ircnet flag. */
123
static void cmd_connect(const char *data)
124
0
{
125
0
  SERVER_CONNECT_REC *conn;
126
0
  SERVER_REC *server;
127
0
        char *rawlog_file;
128
129
0
  conn = get_server_connect(data, NULL, &rawlog_file);
130
0
  if (conn != NULL) {
131
0
    server = server_connect(conn);
132
0
                server_connect_unref(conn);
133
134
0
    if (server != NULL && rawlog_file != NULL)
135
0
      rawlog_open(server->rawlog, rawlog_file);
136
137
0
    g_free(rawlog_file);
138
0
  }
139
0
}
140
141
static RECONNECT_REC *find_reconnect_server(int chat_type,
142
              const char *addr, int port)
143
0
{
144
0
  RECONNECT_REC *match, *last_proto_match;
145
0
  GSList *tmp;
146
0
        int count;
147
148
0
  g_return_val_if_fail(addr != NULL, NULL);
149
150
  /* check if there's a reconnection to the same host and maybe even
151
     the same port */
152
0
        match = last_proto_match = NULL; count = 0;
153
0
  for (tmp = reconnects; tmp != NULL; tmp = tmp->next) {
154
0
    RECONNECT_REC *rec = tmp->data;
155
156
0
    if (rec->conn->chat_type == chat_type) {
157
0
      count++; last_proto_match = rec;
158
0
      if (g_ascii_strcasecmp(rec->conn->address, addr) == 0) {
159
0
        if (rec->conn->port == port)
160
0
          return rec;
161
0
        match = rec;
162
0
      }
163
0
    }
164
0
  }
165
166
0
  if (count == 1) {
167
    /* only one reconnection with wanted protocol,
168
       we probably want to use it */
169
0
                return last_proto_match;
170
0
  }
171
172
0
  return match;
173
0
}
174
175
static void update_reconnection(SERVER_CONNECT_REC *conn, SERVER_REC *server)
176
0
{
177
0
  SERVER_CONNECT_REC *oldconn;
178
0
  RECONNECT_REC *recon;
179
180
0
  if (server != NULL) {
181
0
    oldconn = server->connrec;
182
0
                server_connect_ref(oldconn);
183
0
                reconnect_save_status(conn, server);
184
0
  } else {
185
    /* maybe we can reconnect some server from
186
       reconnection queue */
187
0
    recon = find_reconnect_server(conn->chat_type,
188
0
                conn->address, conn->port);
189
0
    if (recon == NULL) return;
190
191
0
    oldconn = recon->conn;
192
0
                server_connect_ref(oldconn);
193
0
    server_reconnect_destroy(recon);
194
195
0
    conn->away_reason = g_strdup(oldconn->away_reason);
196
0
    conn->channels = g_strdup(oldconn->channels);
197
0
  }
198
199
0
  conn->reconnection = TRUE;
200
201
0
  if (conn->chatnet == NULL && oldconn->chatnet != NULL)
202
0
    conn->chatnet = g_strdup(oldconn->chatnet);
203
204
0
  server_connect_unref(oldconn);
205
0
  if (server != NULL) {
206
0
    signal_emit("command disconnect", 2,
207
0
          "* Changing server", server);
208
0
  }
209
0
}
210
211
static void cmd_server(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
212
0
{
213
0
  command_runsub("server", data, server, item);
214
0
}
215
216
/* SYNTAX: SERVER CONNECT [-4 | -6] [-tls | -notls] [-tls_cert <cert>] [-tls_pkey <pkey>]
217
                  [-tls_pass <password>] [-tls_verify | -notls_verify] [-tls_cafile <cafile>]
218
                  [-tls_capath <capath>] [-tls_ciphers <list>]
219
                  [-tls_pinned_cert <fingerprint>] [-tls_pinned_pubkey <fingerprint>]
220
                  [-!] [-noautosendcmd] [-nocap]
221
                  [-noproxy] [-network <network>] [-host <hostname>]
222
                  [-rawlog <file>]
223
                  [+]<address>|<chatnet> [<port> [<password> [<nick>]]] */
224
/* NOTE: -network replaces the old -ircnet flag. */
225
static void cmd_server_connect(const char *data, SERVER_REC *server)
226
0
{
227
0
  SERVER_CONNECT_REC *conn;
228
0
        char *rawlog_file;
229
0
  int plus_addr;
230
231
0
  g_return_if_fail(data != NULL);
232
233
        /* create connection record */
234
0
  conn = get_server_connect(data, &plus_addr, &rawlog_file);
235
0
  if (conn != NULL) {
236
0
    if (!plus_addr)
237
0
      update_reconnection(conn, server);
238
0
    server = server_connect(conn);
239
0
    server_connect_unref(conn);
240
241
0
    if (server != NULL && rawlog_file != NULL)
242
0
      rawlog_open(server->rawlog, rawlog_file);
243
244
0
    g_free(rawlog_file);
245
0
  }
246
0
}
247
248
/* SYNTAX: DISCONNECT *|<tag> [<message>] */
249
static void cmd_disconnect(const char *data, SERVER_REC *server)
250
0
{
251
0
  char *tag, *msg;
252
0
  void *free_arg;
253
254
0
  g_return_if_fail(data != NULL);
255
256
0
  if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &tag, &msg))
257
0
    return;
258
259
0
  if (*tag != '\0' && g_strcmp0(tag, "*") != 0) {
260
0
    server = server_find_tag(tag);
261
0
    if (server == NULL)
262
0
      server = server_find_lookup_tag(tag);
263
0
  }
264
0
  if (server == NULL) cmd_param_error(CMDERR_NOT_CONNECTED);
265
266
0
  if (*msg == '\0') msg = (char *) settings_get_str("quit_message");
267
0
  signal_emit("server quit", 2, server, msg);
268
269
0
  cmd_params_free(free_arg);
270
0
  server_disconnect(server);
271
0
}
272
273
/* SYNTAX: QUIT [<message>] */
274
static void cmd_quit(const char *data)
275
0
{
276
0
  GSList *tmp, *next;
277
0
  const char *quitmsg;
278
0
  char *str;
279
280
0
  g_return_if_fail(data != NULL);
281
282
0
  quitmsg = *data != '\0' ? data :
283
0
    settings_get_str("quit_message");
284
285
  /* disconnect from every server */
286
0
  for (tmp = servers; tmp != NULL; tmp = next) {
287
0
    next = tmp->next;
288
289
0
    str = g_strdup_printf("* %s", quitmsg);
290
0
    cmd_disconnect(str, tmp->data);
291
0
    g_free(str);
292
0
  }
293
294
0
  signal_emit("gui exit", 0);
295
0
}
296
297
/* SYNTAX: MSG [-<server tag>] [-channel | -nick] *|<targets> <message> */
298
static void cmd_msg(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
299
0
{
300
0
  GHashTable *optlist;
301
0
  char *target, *origtarget, *msg;
302
0
  void *free_arg;
303
0
  int free_ret, target_type = SEND_TARGET_NICK;
304
305
0
  g_return_if_fail(data != NULL);
306
307
0
  if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS |
308
0
          PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
309
0
          "msg", &optlist, &target, &msg))
310
0
    return;
311
0
  if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
312
313
0
  server = cmd_options_get_server("msg", optlist, server);
314
0
  if (server == NULL || !server->connected)
315
0
    cmd_param_error(CMDERR_NOT_CONNECTED);
316
317
0
        origtarget = target;
318
0
  free_ret = FALSE;
319
0
  if (g_strcmp0(target, ",") == 0 || g_strcmp0(target, ".") == 0) {
320
0
    target = parse_special(&target, server, item,
321
0
               NULL, &free_ret, NULL, 0);
322
0
    if (target != NULL && *target == '\0') {
323
0
      if (free_ret)
324
0
        g_free(target);
325
0
      target = NULL;
326
0
      free_ret = FALSE;
327
0
    }
328
0
  }
329
330
0
  if (target != NULL) {
331
0
    if (g_strcmp0(target, "*") == 0) {
332
                        /* send to active channel/query */
333
0
      if (item == NULL)
334
0
        cmd_param_error(CMDERR_NOT_JOINED);
335
336
0
      target_type = IS_CHANNEL(item) ?
337
0
        SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
338
0
      target = (char *) window_item_get_target(item);
339
0
    } else if (g_hash_table_lookup(optlist, "channel") != NULL)
340
0
                        target_type = SEND_TARGET_CHANNEL;
341
0
    else if (g_hash_table_lookup(optlist, "nick") != NULL)
342
0
      target_type = SEND_TARGET_NICK;
343
0
    else {
344
      /* Need to rely on server_ischannel(). If the protocol
345
         doesn't really know if it's channel or nick based on
346
         the name, it should just assume it's nick, because
347
         when typing text to channels it's always sent with
348
         /MSG -channel. */
349
0
      target_type = server_ischannel(server, target) ?
350
0
        SEND_TARGET_CHANNEL : SEND_TARGET_NICK;
351
0
    }
352
0
  }
353
0
  if (target != NULL) {
354
0
    char **splitmsgs;
355
0
    char **tmp = NULL;
356
0
    char *singlemsg[] = { msg, NULL };
357
0
    char *m;
358
0
    int n = 0;
359
360
    /*
361
     * If split_message is NULL, the server doesn't need to split
362
     * long messages.
363
     */
364
0
    if (server->split_message != NULL)
365
0
      splitmsgs = tmp = server->split_message(server, target,
366
0
                msg);
367
0
    else
368
0
      splitmsgs = singlemsg;
369
370
0
    while ((m = splitmsgs[n++])) {
371
0
      signal_emit("server sendmsg", 4, server, target, m,
372
0
            GINT_TO_POINTER(target_type));
373
0
      signal_emit(target_type == SEND_TARGET_CHANNEL ?
374
0
            "message own_public" :
375
0
            "message own_private", 4, server, m,
376
0
            target, origtarget);
377
0
    }
378
0
    g_strfreev(tmp);
379
0
  } else {
380
0
    signal_emit("message own_private", 4, server, msg, target,
381
0
          origtarget);
382
0
  }
383
384
0
  if (free_ret && target != NULL) g_free(target);
385
0
  cmd_params_free(free_arg);
386
0
}
387
388
static void sig_server_sendmsg(SERVER_REC *server, const char *target,
389
             const char *msg, void *target_type_p)
390
0
{
391
0
  server->send_message(server, target, msg,
392
0
           GPOINTER_TO_INT(target_type_p));
393
0
}
394
395
static void cmd_foreach(const char *data, SERVER_REC *server,
396
      WI_ITEM_REC *item)
397
0
{
398
0
  command_runsub("foreach", data, server, item);
399
0
}
400
401
/* SYNTAX: FOREACH SERVER <command> */
402
static void cmd_foreach_server(const char *data, SERVER_REC *server)
403
0
{
404
0
  GSList *list;
405
0
  const char *cmdchars;
406
0
  char *str;
407
408
0
  cmdchars = settings_get_str("cmdchars");
409
0
  str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
410
0
    g_strdup_printf("%c%s", *cmdchars, data);
411
412
0
  list = g_slist_copy(servers);
413
0
  while (list != NULL) {
414
0
    signal_emit("send command", 3, str, list->data, NULL);
415
0
    list = g_slist_remove(list, list->data);
416
0
  }
417
418
0
  g_free(str);
419
0
}
420
421
/* SYNTAX: FOREACH CHANNEL <command> */
422
static void cmd_foreach_channel(const char *data)
423
0
{
424
0
  GSList *list;
425
0
  const char *cmdchars;
426
0
  char *str;
427
428
0
  cmdchars = settings_get_str("cmdchars");
429
0
  str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
430
0
    g_strdup_printf("%c%s", *cmdchars, data);
431
432
0
  list = g_slist_copy(channels);
433
0
  while (list != NULL) {
434
0
    CHANNEL_REC *rec = list->data;
435
436
0
    signal_emit("send command", 3, str, rec->server, rec);
437
0
    list = g_slist_remove(list, list->data);
438
0
  }
439
440
0
  g_free(str);
441
0
}
442
443
/* SYNTAX: FOREACH QUERY <command> */
444
static void cmd_foreach_query(const char *data)
445
0
{
446
0
  GSList *list;
447
0
  const char *cmdchars;
448
0
  char *str;
449
450
0
  cmdchars = settings_get_str("cmdchars");
451
0
  str = strchr(cmdchars, *data) != NULL ? g_strdup(data) :
452
0
    g_strdup_printf("%c%s", *cmdchars, data);
453
454
455
0
  list = g_slist_copy(queries);
456
0
  while (list != NULL) {
457
0
    QUERY_REC *rec = list->data;
458
459
0
    signal_emit("send command", 3, str, rec->server, rec);
460
0
    list = g_slist_remove(list, list->data);
461
0
  }
462
463
0
  g_free(str);
464
0
}
465
466
void chat_commands_init(void)
467
8
{
468
8
  settings_add_str("misc", "quit_message", "leaving");
469
470
8
  command_bind("server", NULL, (SIGNAL_FUNC) cmd_server);
471
8
  command_bind("server connect", NULL, (SIGNAL_FUNC) cmd_server_connect);
472
8
  command_bind("connect", NULL, (SIGNAL_FUNC) cmd_connect);
473
8
  command_bind("disconnect", NULL, (SIGNAL_FUNC) cmd_disconnect);
474
8
  command_bind("quit", NULL, (SIGNAL_FUNC) cmd_quit);
475
8
  command_bind("msg", NULL, (SIGNAL_FUNC) cmd_msg);
476
8
  command_bind("foreach", NULL, (SIGNAL_FUNC) cmd_foreach);
477
8
  command_bind("foreach server", NULL, (SIGNAL_FUNC) cmd_foreach_server);
478
8
  command_bind("foreach channel", NULL, (SIGNAL_FUNC) cmd_foreach_channel);
479
8
  command_bind("foreach query", NULL, (SIGNAL_FUNC) cmd_foreach_query);
480
481
8
  signal_add("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
482
483
8
  command_set_options(
484
8
      "connect",
485
8
      "4 6 !! -network ~ssl ~+ssl_cert ~+ssl_pkey ~+ssl_pass ~ssl_verify ~+ssl_cafile "
486
8
      "~+ssl_capath ~+ssl_ciphers ~+ssl_pinned_cert ~+ssl_pinned_pubkey tls notls +tls_cert "
487
8
      "+tls_pkey +tls_pass tls_verify notls_verify +tls_cafile +tls_capath +tls_ciphers "
488
8
      "+tls_pinned_cert +tls_pinned_pubkey +host noproxy -rawlog noautosendcmd");
489
8
  command_set_options("msg", "channel nick");
490
8
}
491
492
void chat_commands_deinit(void)
493
0
{
494
0
  command_unbind("server", (SIGNAL_FUNC) cmd_server);
495
0
  command_unbind("server connect", (SIGNAL_FUNC) cmd_server_connect);
496
0
  command_unbind("connect", (SIGNAL_FUNC) cmd_connect);
497
0
  command_unbind("disconnect", (SIGNAL_FUNC) cmd_disconnect);
498
0
  command_unbind("quit", (SIGNAL_FUNC) cmd_quit);
499
0
  command_unbind("msg", (SIGNAL_FUNC) cmd_msg);
500
0
  command_unbind("foreach", (SIGNAL_FUNC) cmd_foreach);
501
0
  command_unbind("foreach server", (SIGNAL_FUNC) cmd_foreach_server);
502
0
  command_unbind("foreach channel", (SIGNAL_FUNC) cmd_foreach_channel);
503
0
  command_unbind("foreach query", (SIGNAL_FUNC) cmd_foreach_query);
504
505
  signal_remove("server sendmsg", (SIGNAL_FUNC) sig_server_sendmsg);
506
0
}