Coverage Report

Created: 2025-07-11 07:30

/src/irssi/src/fe-common/irc/fe-netsplit.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 fe-netsplit.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/fe-common/irc/module-formats.h>
23
#include <irssi/src/core/signals.h>
24
#include <irssi/src/core/levels.h>
25
#include <irssi/src/core/settings.h>
26
27
#include <irssi/src/irc/core/irc-servers.h>
28
#include <irssi/src/irc/core/irc-commands.h>
29
#include <irssi/src/core/ignore.h>
30
#include <irssi/src/irc/core/netsplit.h>
31
32
#include <irssi/src/fe-common/core/printtext.h>
33
34
0
#define SPLIT_WAIT_TIME 5 /* how many seconds to wait for the QUIT split messages to stop */
35
36
static int split_tag;
37
static int netsplit_max_nicks, netsplit_nicks_hide_threshold;
38
static int printing_splits;
39
40
static int get_last_split(IRC_SERVER_REC *server)
41
0
{
42
0
  GSList *tmp;
43
0
  time_t last;
44
45
0
        last = 0;
46
0
  for (tmp = server->split_servers; tmp != NULL; tmp = tmp->next) {
47
0
    NETSPLIT_SERVER_REC *rec = tmp->data;
48
49
0
    if (rec->last > last) last = rec->last;
50
0
  }
51
52
0
  return last;
53
0
}
54
55
typedef struct {
56
  char *name;
57
  int nick_count, maxnickpos;
58
  GString *nicks;
59
} TEMP_SPLIT_CHAN_REC;
60
61
typedef struct {
62
        IRC_SERVER_REC *server_rec;
63
  GSList *servers; /* if many servers splitted from the same one */
64
  GSList *channels;
65
} TEMP_SPLIT_REC;
66
67
static GSList *get_source_servers(const char *server, GSList **servers)
68
0
{
69
0
  GSList *list, *next, *tmp;
70
71
0
  list = NULL;
72
0
  for (tmp = *servers; tmp != NULL; tmp = next) {
73
0
    NETSPLIT_SERVER_REC *rec = tmp->data;
74
0
    next = tmp->next;
75
76
0
    if (g_ascii_strcasecmp(rec->server, server) == 0) {
77
0
      rec->prints = 0;
78
0
      list = g_slist_append(list, rec);
79
0
      *servers = g_slist_remove(*servers, rec);
80
0
    }
81
0
  }
82
83
0
  return list;
84
0
}
85
86
static TEMP_SPLIT_CHAN_REC *find_split_chan(TEMP_SPLIT_REC *rec,
87
              const char *name)
88
0
{
89
0
  GSList *tmp;
90
91
0
  for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
92
0
    TEMP_SPLIT_CHAN_REC *chanrec = tmp->data;
93
94
0
    if (g_ascii_strcasecmp(chanrec->name, name) == 0)
95
0
      return chanrec;
96
0
  }
97
98
0
  return NULL;
99
0
}
100
101
static void get_server_splits(void *key, NETSPLIT_REC *split,
102
            TEMP_SPLIT_REC *rec)
103
0
{
104
0
  TEMP_SPLIT_CHAN_REC *chanrec;
105
0
  GSList *tmp;
106
107
0
  if (split->printed ||
108
0
      g_slist_find(rec->servers, split->server) == NULL)
109
0
    return;
110
111
0
  split->printed = TRUE;
112
0
  for (tmp = split->channels; tmp != NULL; tmp = tmp->next) {
113
0
    NETSPLIT_CHAN_REC *splitchan = tmp->data;
114
115
0
    if (ignore_check(SERVER(rec->server_rec), split->nick,
116
0
         split->address, splitchan->name, "",
117
0
         MSGLEVEL_QUITS))
118
0
      continue;
119
120
0
    chanrec = find_split_chan(rec, splitchan->name);
121
0
    if (chanrec == NULL) {
122
0
      chanrec = g_new0(TEMP_SPLIT_CHAN_REC, 1);
123
0
      chanrec->name = splitchan->name;
124
0
      chanrec->nicks = g_string_new(NULL);
125
126
0
      rec->channels = g_slist_append(rec->channels, chanrec);
127
0
    }
128
129
0
    split->server->prints++;
130
0
    chanrec->nick_count++;
131
0
    if (netsplit_nicks_hide_threshold <= 0 ||
132
0
        chanrec->nick_count <= netsplit_nicks_hide_threshold) {
133
0
      if (splitchan->op)
134
0
        g_string_append_c(chanrec->nicks, '@');
135
0
      else if (splitchan->voice)
136
0
        g_string_append_c(chanrec->nicks, '+');
137
0
      g_string_append_printf(chanrec->nicks, "%s, ", split->nick);
138
139
0
      if (chanrec->nick_count == netsplit_max_nicks)
140
0
                                chanrec->maxnickpos = chanrec->nicks->len;
141
0
    }
142
0
  }
143
0
}
144
145
static void print_server_splits(IRC_SERVER_REC *server, TEMP_SPLIT_REC *rec, const char *filter_channel)
146
0
{
147
0
  GString *destservers;
148
0
  char *sourceserver;
149
0
  GSList *tmp;
150
151
0
  g_return_if_fail(rec->servers != NULL);
152
153
0
  destservers = g_string_new(NULL);
154
0
  for (tmp = rec->servers; tmp != NULL; tmp = tmp->next) {
155
0
    NETSPLIT_SERVER_REC *rec = tmp->data;
156
157
0
    if (rec->prints > 0) {
158
0
      g_string_append_printf(destservers, "%s, ",
159
0
            rec->destserver);
160
0
    }
161
0
  }
162
0
  if (destservers->len == 0) {
163
                /* no nicks to print in this server */
164
0
    g_string_free(destservers, TRUE);
165
0
    return;
166
0
  }
167
0
  g_string_truncate(destservers, destservers->len-2);
168
169
0
  sourceserver = ((NETSPLIT_SERVER_REC *) (rec->servers->data))->server;
170
0
  for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
171
0
    TEMP_SPLIT_CHAN_REC *chan = tmp->data;
172
173
0
    if (filter_channel != NULL &&
174
0
        strcasecmp(chan->name, filter_channel) != 0)
175
0
      continue;
176
177
0
    g_string_truncate(chan->nicks, chan->nicks->len-2);
178
179
0
    if (netsplit_max_nicks > 0 &&
180
0
        chan->nick_count > netsplit_max_nicks) {
181
0
      g_string_truncate(chan->nicks, chan->maxnickpos);
182
0
      printformat(server, chan->name, MSGLEVEL_QUITS,
183
0
            IRCTXT_NETSPLIT_MORE, sourceserver,
184
0
            destservers->str, chan->nicks->str,
185
0
            chan->nick_count - netsplit_max_nicks);
186
0
    } else {
187
0
      printformat(server, chan->name, MSGLEVEL_QUITS,
188
0
            IRCTXT_NETSPLIT, sourceserver,
189
0
            destservers->str, chan->nicks->str);
190
0
    }
191
0
  }
192
193
0
  g_string_free(destservers, TRUE);
194
0
}
195
196
static void temp_split_chan_free(TEMP_SPLIT_CHAN_REC *rec)
197
0
{
198
0
  g_string_free(rec->nicks, TRUE);
199
0
  g_free(rec);
200
0
}
201
202
static void print_splits(IRC_SERVER_REC *server, const char *filter_channel)
203
0
{
204
0
  TEMP_SPLIT_REC temp;
205
0
  GSList *servers;
206
207
0
  printing_splits = TRUE;
208
209
0
  servers = g_slist_copy(server->split_servers);
210
0
  while (servers != NULL) {
211
0
    NETSPLIT_SERVER_REC *sserver = servers->data;
212
213
    /* get all the splitted servers that have the same
214
       source server */
215
0
                temp.servers = get_source_servers(sserver->server, &servers);
216
0
                temp.server_rec = server;
217
0
    temp.channels = NULL;
218
219
0
    g_hash_table_foreach(server->splits,
220
0
             (GHFunc) get_server_splits, &temp);
221
0
    print_server_splits(server, &temp, filter_channel);
222
223
0
    g_slist_foreach(temp.channels,
224
0
        (GFunc) temp_split_chan_free, NULL);
225
0
    g_slist_free(temp.servers);
226
0
    g_slist_free(temp.channels);
227
0
  }
228
229
0
  printing_splits = FALSE;
230
0
}
231
232
static int check_server_splits(IRC_SERVER_REC *server)
233
0
{
234
0
  time_t last;
235
236
0
  g_return_val_if_fail(IS_IRC_SERVER(server), FALSE);
237
238
0
  last = get_last_split(server);
239
0
  if (time(NULL)-last < SPLIT_WAIT_TIME)
240
0
    return FALSE;
241
242
0
  print_splits(server, NULL);
243
0
        return TRUE;
244
0
}
245
246
/* something is going to be printed to screen, print our current netsplit
247
   message before it. */
248
static void sig_print_starting(TEXT_DEST_REC *dest)
249
0
{
250
0
  IRC_SERVER_REC *rec;
251
252
0
  if (printing_splits)
253
0
    return;
254
255
0
  if (!IS_IRC_SERVER(dest->server))
256
0
    return;
257
258
0
  rec = IRC_SERVER(dest->server);
259
0
  if (rec->split_servers != NULL) {
260
    /* if split_servers exists, the server rec should be
261
       still valid. otherwise, calling server->ischannel
262
       may not be safe. */
263
0
    if (dest->target != NULL && !server_ischannel((SERVER_REC *) rec, dest->target))
264
0
      return;
265
266
0
    print_splits(rec, NULL);
267
0
  }
268
0
}
269
270
static int sig_check_splits(void)
271
0
{
272
0
  GSList *tmp;
273
0
  int stop;
274
275
0
  stop = TRUE;
276
0
  for (tmp = servers; tmp != NULL; tmp = tmp->next) {
277
0
    IRC_SERVER_REC *rec = tmp->data;
278
279
0
    if (!IS_IRC_SERVER(rec))
280
0
      continue;
281
282
0
    if (rec->split_servers != NULL) {
283
0
      if (!check_server_splits(rec))
284
0
        stop = FALSE;
285
0
    }
286
0
  }
287
288
0
  if (stop) {
289
0
    g_source_remove(split_tag);
290
0
    signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
291
0
                split_tag = -1;
292
0
  }
293
0
  return 1;
294
0
}
295
296
static void sig_netsplit_servers(void)
297
0
{
298
0
  if (settings_get_bool("hide_netsplit_quits") && split_tag == -1) {
299
0
    split_tag = g_timeout_add(1000,
300
0
            (GSourceFunc) sig_check_splits,
301
0
            NULL);
302
0
    signal_add("print starting", (SIGNAL_FUNC) sig_print_starting);
303
0
  }
304
0
}
305
306
static int split_equal(NETSPLIT_REC *n1, NETSPLIT_REC *n2)
307
0
{
308
0
        return g_ascii_strcasecmp(n1->nick, n2->nick);
309
0
}
310
311
static void split_get(void *key, NETSPLIT_REC *rec, GSList **list)
312
0
{
313
0
  *list = g_slist_insert_sorted(*list, rec,
314
0
              (GCompareFunc) split_equal);
315
0
}
316
317
static void split_print(NETSPLIT_REC *rec, SERVER_REC *server)
318
0
{
319
0
  NETSPLIT_CHAN_REC *chan;
320
0
        char *chanstr;
321
322
0
  chan = rec->channels->data;
323
0
  chanstr = chan == NULL ?
324
0
                      g_strdup("") :
325
0
                      g_strconcat(chan->op ? "@" : (chan->voice ? "+" : ""), chan->name, NULL);
326
327
0
  printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_LINE,
328
0
        rec->nick, chanstr, rec->server->server,
329
0
        rec->server->destserver);
330
331
0
  g_free(chanstr);
332
0
}
333
334
/* SYNTAX: NETSPLIT */
335
static void cmd_netsplit(const char *data, IRC_SERVER_REC *server)
336
0
{
337
0
  GSList *list;
338
339
0
        CMD_IRC_SERVER(server);
340
341
0
  if (server->split_servers == NULL) {
342
0
    printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
343
0
          IRCTXT_NO_NETSPLITS);
344
0
    return;
345
0
  }
346
347
0
  printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_HEADER);
348
349
0
        list = NULL;
350
0
  g_hash_table_foreach(server->splits, (GHFunc) split_get, &list);
351
0
  g_slist_foreach(list, (GFunc) split_print, server);
352
0
        g_slist_free(list);
353
354
0
  printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_FOOTER);
355
0
}
356
357
static void read_settings(void)
358
2
{
359
2
        netsplit_max_nicks = settings_get_int("netsplit_max_nicks");
360
2
  netsplit_nicks_hide_threshold =
361
2
    settings_get_int("netsplit_nicks_hide_threshold");
362
2
  if (netsplit_nicks_hide_threshold < netsplit_max_nicks)
363
0
    netsplit_max_nicks = netsplit_nicks_hide_threshold;
364
2
}
365
366
void fe_netsplit_init(void)
367
2
{
368
2
  settings_add_int("misc", "netsplit_max_nicks", 10);
369
2
  settings_add_int("misc", "netsplit_nicks_hide_threshold", 15);
370
2
  split_tag = -1;
371
2
  printing_splits = FALSE;
372
373
2
  read_settings();
374
2
  signal_add("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers);
375
2
  signal_add("setup changed", (SIGNAL_FUNC) read_settings);
376
2
  command_bind_irc("netsplit", NULL, (SIGNAL_FUNC) cmd_netsplit);
377
2
}
378
379
void fe_netsplit_deinit(void)
380
0
{
381
0
  if (split_tag != -1) {
382
0
    g_source_remove(split_tag);
383
0
    signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
384
0
  }
385
386
0
  signal_remove("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers);
387
0
  signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
388
0
  command_unbind("netsplit", (SIGNAL_FUNC) cmd_netsplit);
389
0
}