source: irc_channel.c @ d797fb4

Last change on this file since d797fb4 was b6a3fbf, checked in by dequis <dx@…>, at 2015-08-27T07:25:07Z

irc_channel_name_gen: handle g_convert_with_fallback failures

First fallback to ASCII without TRANSLIT, and if that fails too, just
give up by returning NULL.

Basically the same thing as 3a27896 (a netbsd specific fix), but for
channel names. This wasn't needed before because the older version of
this code caught the NULL from the ASCIITRANSLIT attempt and gave up
immediately, while the refactored version lacked null checking.

  • Property mode set to 100644
File size: 22.3 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2013 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* The IRC-based UI - Representing (virtual) channels.                  */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include "bitlbee.h"
27
28static char *set_eval_channel_type(set_t *set, char *value);
29static gint irc_channel_user_cmp(gconstpointer a_, gconstpointer b_);
30static const struct irc_channel_funcs control_channel_funcs;
31
32extern const struct irc_channel_funcs irc_channel_im_chat_funcs;
33
34irc_channel_t *irc_channel_new(irc_t *irc, const char *name)
35{
36        irc_channel_t *ic;
37        set_t *s;
38
39        if (!irc_channel_name_ok(name) || irc_channel_by_name(irc, name)) {
40                return NULL;
41        }
42
43        ic = g_new0(irc_channel_t, 1);
44        ic->irc = irc;
45        ic->name = g_strdup(name);
46        strcpy(ic->mode, CMODE);
47
48        irc_channel_add_user(ic, irc->root);
49
50        irc->channels = g_slist_append(irc->channels, ic);
51
52        set_add(&ic->set, "auto_join", "false", set_eval_bool, ic);
53
54        s = set_add(&ic->set, "type", "control", set_eval_channel_type, ic);
55        s->flags |= SET_NOSAVE;    /* Layer violation (XML format detail) */
56
57        if (name[0] == '&') {
58                set_setstr(&ic->set, "type", "control");
59        } else { /* if( name[0] == '#' ) */
60                set_setstr(&ic->set, "type", "chat");
61        }
62
63        return ic;
64}
65
66irc_channel_t *irc_channel_by_name(irc_t *irc, const char *name)
67{
68        GSList *l;
69
70        for (l = irc->channels; l; l = l->next) {
71                irc_channel_t *ic = l->data;
72
73                if (irc_channel_name_cmp(name, ic->name) == 0) {
74                        return ic;
75                }
76        }
77
78        return NULL;
79}
80
81irc_channel_t *irc_channel_get(irc_t *irc, char *id)
82{
83        irc_channel_t *ic, *ret = NULL;
84        GSList *l;
85        int nr;
86
87        if (sscanf(id, "%d", &nr) == 1 && nr < 1000) {
88                for (l = irc->channels; l; l = l->next) {
89                        ic = l->data;
90                        if ((nr--) == 0) {
91                                return ic;
92                        }
93                }
94
95                return NULL;
96        }
97
98        /* Exact match first: Partial match only sucks if there's a channel
99           #aa and #aabb */
100        if ((ret = irc_channel_by_name(irc, id))) {
101                return ret;
102        }
103
104        for (l = irc->channels; l; l = l->next) {
105                ic = l->data;
106
107                if (strstr(ic->name, id)) {
108                        /* Make sure it's a unique match. */
109                        if (!ret) {
110                                ret = ic;
111                        } else {
112                                return NULL;
113                        }
114                }
115        }
116
117        return ret;
118}
119
120int irc_channel_free(irc_channel_t *ic)
121{
122        irc_t *irc;
123        GSList *l;
124
125        if (ic == NULL) {
126                return 0;
127        }
128        irc = ic->irc;
129
130        if (ic->flags & IRC_CHANNEL_JOINED) {
131                irc_channel_del_user(ic, irc->user, IRC_CDU_KICK, "Cleaning up channel");
132        }
133
134        if (ic->f->_free) {
135                ic->f->_free(ic);
136        }
137
138        while (ic->set) {
139                set_del(&ic->set, ic->set->key);
140        }
141
142        irc->channels = g_slist_remove(irc->channels, ic);
143        while (ic->users) {
144                g_free(ic->users->data);
145                ic->users = g_slist_remove(ic->users, ic->users->data);
146        }
147
148        for (l = irc->users; l; l = l->next) {
149                irc_user_t *iu = l->data;
150
151                if (iu->last_channel == ic) {
152                        iu->last_channel = irc->default_channel;
153                }
154        }
155
156        if (ic->pastebuf_timer) {
157                b_event_remove(ic->pastebuf_timer);
158        }
159
160        g_free(ic->name);
161        g_free(ic->topic);
162        g_free(ic->topic_who);
163        g_free(ic);
164
165        return 1;
166}
167
168struct irc_channel_free_data {
169        irc_t *irc;
170        irc_channel_t *ic;
171        char *name;
172};
173
174static gboolean irc_channel_free_callback(gpointer data, gint fd, b_input_condition cond)
175{
176        struct irc_channel_free_data *d = data;
177
178        if (g_slist_find(irc_connection_list, d->irc) &&
179            irc_channel_by_name(d->irc, d->name) == d->ic &&
180            !(d->ic->flags & IRC_CHANNEL_JOINED)) {
181                irc_channel_free(d->ic);
182        }
183
184        g_free(d->name);
185        g_free(d);
186        return FALSE;
187}
188
189/* Free the channel, but via the event loop, so after finishing whatever event
190   we're currently handling. */
191void irc_channel_free_soon(irc_channel_t *ic)
192{
193        struct irc_channel_free_data *d = g_new0(struct irc_channel_free_data, 1);
194
195        d->irc = ic->irc;
196        d->ic = ic;
197        d->name = g_strdup(ic->name);
198
199        b_timeout_add(0, irc_channel_free_callback, d);
200}
201
202static char *set_eval_channel_type(set_t *set, char *value)
203{
204        struct irc_channel *ic = set->data;
205        const struct irc_channel_funcs *new;
206
207        if (strcmp(value, "control") == 0) {
208                new = &control_channel_funcs;
209        } else if (ic != ic->irc->default_channel && strcmp(value, "chat") == 0) {
210                new = &irc_channel_im_chat_funcs;
211        } else {
212                return SET_INVALID;
213        }
214
215        /* TODO: Return values. */
216        if (ic->f && ic->f->_free) {
217                ic->f->_free(ic);
218        }
219
220        ic->f = new;
221
222        if (ic->f && ic->f->_init) {
223                ic->f->_init(ic);
224        }
225
226        return value;
227}
228
229int irc_channel_add_user(irc_channel_t *ic, irc_user_t *iu)
230{
231        irc_channel_user_t *icu;
232
233        if (irc_channel_has_user(ic, iu)) {
234                return 0;
235        }
236
237        icu = g_new0(irc_channel_user_t, 1);
238        icu->iu = iu;
239
240        ic->users = g_slist_insert_sorted(ic->users, icu, irc_channel_user_cmp);
241
242        irc_channel_update_ops(ic, set_getstr(&ic->irc->b->set, "ops"));
243
244        if (iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED) {
245                ic->flags |= IRC_CHANNEL_JOINED;
246                irc_send_join(ic, iu);
247        }
248
249        return 1;
250}
251
252int irc_channel_del_user(irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg)
253{
254        irc_channel_user_t *icu;
255
256        if (!(icu = irc_channel_has_user(ic, iu))) {
257                return 0;
258        }
259
260        ic->users = g_slist_remove(ic->users, icu);
261        g_free(icu);
262
263        if (!(ic->flags & IRC_CHANNEL_JOINED) || type == IRC_CDU_SILENT) {
264        }
265        /* Do nothing. The caller should promise it won't screw
266           up state of the IRC client. :-) */
267        else if (type == IRC_CDU_PART) {
268                irc_send_part(ic, iu, msg);
269        } else if (type == IRC_CDU_KICK) {
270                irc_send_kick(ic, iu, ic->irc->root, msg);
271        }
272
273        if (iu == ic->irc->user) {
274                ic->flags &= ~IRC_CHANNEL_JOINED;
275
276                if (ic->irc->status & USTATUS_SHUTDOWN) {
277                        /* Don't do anything fancy when we're shutting down anyway. */
278                } else if (ic->flags & IRC_CHANNEL_TEMP) {
279                        irc_channel_free_soon(ic);
280                } else {
281                        /* Flush userlist now. The user won't see it anyway. */
282                        while (ic->users) {
283                                g_free(ic->users->data);
284                                ic->users = g_slist_remove(ic->users, ic->users->data);
285                        }
286                        irc_channel_add_user(ic, ic->irc->root);
287                }
288        }
289
290        return 1;
291}
292
293irc_channel_user_t *irc_channel_has_user(irc_channel_t *ic, irc_user_t *iu)
294{
295        GSList *l;
296
297        for (l = ic->users; l; l = l->next) {
298                irc_channel_user_t *icu = l->data;
299
300                if (icu->iu == iu) {
301                        return icu;
302                }
303        }
304
305        return NULL;
306}
307
308/* Find a channel we're currently in, that currently has iu in it. */
309struct irc_channel *irc_channel_with_user(irc_t *irc, irc_user_t *iu)
310{
311        GSList *l;
312
313        for (l = irc->channels; l; l = l->next) {
314                irc_channel_t *ic = l->data;
315
316                if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
317                        continue;
318                }
319
320                if ((ic->flags & IRC_CHANNEL_JOINED) &&
321                    irc_channel_has_user(ic, iu)) {
322                        return ic;
323                }
324        }
325
326        /* If there was no match, try once more but just see if the user
327           *would* be in the channel, i.e. if s/he were online. */
328        if (iu->bu == NULL) {
329                return NULL;
330        }
331
332        for (l = irc->channels; l; l = l->next) {
333                irc_channel_t *ic = l->data;
334
335                if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
336                        continue;
337                }
338
339                if ((ic->flags & IRC_CHANNEL_JOINED) &&
340                    irc_channel_wants_user(ic, iu)) {
341                        return ic;
342                }
343        }
344
345        return NULL;
346}
347
348int irc_channel_set_topic(irc_channel_t *ic, const char *topic, const irc_user_t *iu)
349{
350        g_free(ic->topic);
351        ic->topic = g_strdup(topic);
352
353        g_free(ic->topic_who);
354        if (iu) {
355                ic->topic_who = g_strdup_printf("%s!%s@%s", iu->nick, iu->user, iu->host);
356        } else {
357                ic->topic_who = NULL;
358        }
359
360        ic->topic_time = time(NULL);
361
362        if (ic->flags & IRC_CHANNEL_JOINED) {
363                irc_send_topic(ic, TRUE);
364        }
365
366        return 1;
367}
368
369void irc_channel_user_set_mode(irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags)
370{
371        irc_channel_user_t *icu = irc_channel_has_user(ic, iu);
372
373        if (!icu || icu->flags == flags) {
374                return;
375        }
376
377        if (ic->flags & IRC_CHANNEL_JOINED) {
378                irc_send_channel_user_mode_diff(ic, iu, icu->flags, flags);
379        }
380
381        icu->flags = flags;
382}
383
384void irc_channel_set_mode(irc_channel_t *ic, const char *s)
385{
386        irc_t *irc = ic->irc;
387        char m[128], st = 1;
388        const char *t;
389        int i;
390        char changes[512], *p, st2 = 2;
391
392        memset(m, 0, sizeof(m));
393
394        for (t = ic->mode; *t; t++) {
395                if (*t < sizeof(m)) {
396                        m[(int) *t] = 1;
397                }
398        }
399
400        p = changes;
401        for (t = s; *t; t++) {
402                if (*t == '+' || *t == '-') {
403                        st = *t == '+';
404                } else if (strchr(CMODES, *t)) {
405                        if (m[(int) *t] != st) {
406                                if (st != st2) {
407                                        st2 = st, *p++ = st ? '+' : '-';
408                                }
409                                *p++ = *t;
410                        }
411                        m[(int) *t] = st;
412                }
413        }
414        *p = '\0';
415
416        memset(ic->mode, 0, sizeof(ic->mode));
417
418        for (i = 'A'; i <= 'z' && strlen(ic->mode) < (sizeof(ic->mode) - 1); i++) {
419                if (m[i]) {
420                        ic->mode[strlen(ic->mode)] = i;
421                }
422        }
423
424        if (*changes && (ic->flags & IRC_CHANNEL_JOINED)) {
425                irc_write(irc, ":%s!%s@%s MODE %s :%s", irc->root->nick,
426                          irc->root->user, irc->root->host, ic->name,
427                          changes);
428        }
429}
430
431void irc_channel_auto_joins(irc_t *irc, account_t *acc)
432{
433        GSList *l;
434
435        for (l = irc->channels; l; l = l->next) {
436                irc_channel_t *ic = l->data;
437                gboolean aj = set_getbool(&ic->set, "auto_join");
438                char *type;
439
440                if (acc &&
441                    (type = set_getstr(&ic->set, "chat_type")) &&
442                    strcmp(type, "room") == 0) {
443                        /* Bit of an ugly special case: Handle chatrooms here, we
444                           can only auto-join them if their account is online. */
445                        char *acc_s;
446
447                        if (!aj && !(ic->flags & IRC_CHANNEL_JOINED)) {
448                                /* Only proceed if this one's marked as auto_join
449                                   or if we're in it already. (Very likely the IRC
450                                   client auto-(re)joining at reconnect time.) */
451                                continue;
452                        } else if (!(acc_s = set_getstr(&ic->set, "account"))) {
453                                continue;
454                        } else if (account_get(irc->b, acc_s) != acc) {
455                                continue;
456                        } else if (acc->ic == NULL || !(acc->ic->flags & OPT_LOGGED_IN)) {
457                                continue;
458                        } else {
459                                ic->f->join(ic);
460                        }
461                } else if (aj) {
462                        irc_channel_add_user(ic, irc->user);
463                }
464        }
465}
466
467void irc_channel_printf(irc_channel_t *ic, char *format, ...)
468{
469        va_list params;
470        char *text;
471
472        va_start(params, format);
473        text = g_strdup_vprintf(format, params);
474        va_end(params);
475
476        irc_send_msg(ic->irc->root, "PRIVMSG", ic->name, text, NULL);
477        g_free(text);
478}
479
480gboolean irc_channel_name_ok(const char *name_)
481{
482        const unsigned char *name = (unsigned char *) name_;
483        int i;
484
485        if (name_[0] == '\0') {
486                return FALSE;
487        }
488
489        /* Check if the first character is in CTYPES (#&) */
490        if (strchr(CTYPES, name_[0]) == NULL) {
491                return FALSE;
492        }
493
494        /* RFC 1459 keeps amazing me: While only a "few" chars are allowed
495           in nicknames, channel names can be pretty much anything as long
496           as they start with # or &. I'll be a little bit more strict and
497           disallow all non-printable characters. */
498        for (i = 1; name[i]; i++) {
499                if (name[i] <= ' ' || name[i] == ',') {
500                        return FALSE;
501                }
502        }
503
504        return TRUE;
505}
506
507void irc_channel_name_strip(char *name)
508{
509        int i, j;
510
511        for (i = j = 0; name[i]; i++) {
512                if (name[i] > ' ' && name[i] != ',') {
513                        name[j++] = name[i];
514                }
515        }
516
517        name[j] = '\0';
518}
519
520int irc_channel_name_cmp(const char *a_, const char *b_)
521{
522        static unsigned char case_map[256];
523        const unsigned char *a = (unsigned char *) a_, *b = (unsigned char *) b_;
524        int i;
525
526        if (case_map['A'] == '\0') {
527                for (i = 33; i < 256; i++) {
528                        if (i != ',') {
529                                case_map[i] = i;
530                        }
531                }
532
533                for (i = 0; i < 26; i++) {
534                        case_map['A' + i] = 'a' + i;
535                }
536
537                case_map['['] = '{';
538                case_map[']'] = '}';
539                case_map['~'] = '`';
540                case_map['\\'] = '|';
541        }
542
543        if (!irc_channel_name_ok(a_) || !irc_channel_name_ok(b_)) {
544                return -1;
545        }
546
547        for (i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i++) {
548                if (case_map[a[i]] == case_map[b[i]]) {
549                        continue;
550                } else {
551                        return case_map[a[i]] - case_map[b[i]];
552                }
553        }
554
555        return case_map[a[i]] - case_map[b[i]];
556}
557
558gboolean irc_channel_is_unused(irc_t *irc, char *name)
559{
560        char *type, *chat_type;
561        irc_channel_t *oic;
562
563        if (!irc_channel_name_ok(name)) {
564                return FALSE;
565        }
566
567        if (!(oic = irc_channel_by_name(irc, name))) {
568                return TRUE;
569        }
570
571        type = set_getstr(&oic->set, "type");
572        chat_type = set_getstr(&oic->set, "chat_type");
573
574        if (type && chat_type && oic->data == FALSE &&
575            strcmp(type, "chat") == 0 &&
576            strcmp(chat_type, "groupchat") == 0) {
577                /* There's a channel with this name already, but it looks
578                   like it's not in use yet. Most likely the IRC client
579                   rejoined the channel after a reconnect. Remove it so
580                   we can reuse its name. */
581                irc_channel_free(oic);
582                return TRUE;
583        }
584
585        return FALSE;
586}
587
588char *irc_channel_name_gen(irc_t *irc, const char *hint)
589{
590        char name[MAX_NICK_LENGTH + 1] = { 0 };
591        char *translit_name;
592        gsize bytes_written;
593
594        translit_name = g_convert_with_fallback(hint, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, &bytes_written, NULL);
595
596        if (!translit_name) {
597                /* Same thing as in nick_gen() in nick.c, try again without //TRANSLIT */
598                translit_name = g_convert_with_fallback(hint, -1, "ASCII", "UTF-8", "", NULL, &bytes_written, NULL);
599        }
600
601        if (!translit_name) {
602                return NULL;
603        }
604
605        if (bytes_written > MAX_NICK_LENGTH) {
606                translit_name[MAX_NICK_LENGTH] = '\0';
607        }
608
609        name[0] = '#';
610        strncpy(name + 1, translit_name, MAX_NICK_LENGTH - 1);
611        name[MAX_NICK_LENGTH] = '\0';
612
613        g_free(translit_name);
614
615        irc_channel_name_strip(name);
616
617        if (set_getbool(&irc->b->set, "lcnicks")) {
618                nick_lc(irc, name + 1);
619        }
620
621        while (!irc_channel_is_unused(irc, name)) {
622                underscore_dedupe(name);
623        }
624
625        return g_strdup(name);
626}
627
628gboolean irc_channel_name_hint(irc_channel_t *ic, const char *name)
629{
630        irc_t *irc = ic->irc;
631        char *full_name;
632
633        /* Don't rename a channel if the user's in it already. */
634        if (ic->flags & IRC_CHANNEL_JOINED) {
635                return FALSE;
636        }
637
638        if (!(full_name = irc_channel_name_gen(irc, name))) {
639                return FALSE;
640        }
641
642        g_free(ic->name);
643        ic->name = full_name;
644
645        return TRUE;
646}
647
648static gint irc_channel_user_cmp(gconstpointer a_, gconstpointer b_)
649{
650        const irc_channel_user_t *a = a_, *b = b_;
651
652        return irc_user_cmp(a->iu, b->iu);
653}
654
655void irc_channel_update_ops(irc_channel_t *ic, char *value)
656{
657        irc_channel_user_set_mode(ic, ic->irc->root,
658                                  (strcmp(value, "both") == 0 ||
659                                   strcmp(value, "root") == 0) ? IRC_CHANNEL_USER_OP : 0);
660        irc_channel_user_set_mode(ic, ic->irc->user,
661                                  (strcmp(value, "both") == 0 ||
662                                   strcmp(value, "user") == 0) ? IRC_CHANNEL_USER_OP : 0);
663}
664
665char *set_eval_irc_channel_ops(set_t *set, char *value)
666{
667        irc_t *irc = set->data;
668        GSList *l;
669
670        if (strcmp(value, "both") != 0 && strcmp(value, "none") != 0 &&
671            strcmp(value, "user") != 0 && strcmp(value, "root") != 0) {
672                return SET_INVALID;
673        }
674
675        for (l = irc->channels; l; l = l->next) {
676                irc_channel_update_ops(l->data, value);
677        }
678
679        return value;
680}
681
682/* Channel-type dependent functions, for control channels: */
683static gboolean control_channel_privmsg(irc_channel_t *ic, const char *msg)
684{
685        irc_t *irc = ic->irc;
686        irc_user_t *iu;
687        const char *s;
688
689        /* Scan for non-whitespace chars followed by a colon: */
690        for (s = msg; *s && !g_ascii_isspace(*s) && *s != ':' && *s != ','; s++) {
691        }
692
693        if (*s == ':' || *s == ',') {
694                char to[s - msg + 1];
695
696                memset(to, 0, sizeof(to));
697                strncpy(to, msg, s - msg);
698                while (*(++s) && g_ascii_isspace(*s)) {
699                }
700                msg = s;
701
702                if (!(iu = irc_user_by_name(irc, to))) {
703                        irc_channel_printf(ic, "User does not exist: %s", to);
704                } else {
705                        ic->last_target = iu;
706                }
707        } else if (g_strcasecmp(set_getstr(&irc->b->set, "default_target"), "last") == 0 &&
708                   ic->last_target && g_slist_find(irc->users, ic->last_target)) {
709                iu = ic->last_target;
710        } else {
711                iu = irc->root;
712        }
713
714        if (iu && iu->f->privmsg) {
715                iu->last_channel = ic;
716                iu->f->privmsg(iu, msg);
717        }
718
719        return TRUE;
720}
721
722static gboolean control_channel_invite(irc_channel_t *ic, irc_user_t *iu)
723{
724        struct irc_control_channel *icc = ic->data;
725        bee_user_t *bu = iu->bu;
726
727        if (bu == NULL) {
728                return FALSE;
729        }
730
731        if (icc->type != IRC_CC_TYPE_GROUP) {
732                irc_send_num(ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name);
733                return FALSE;
734        }
735
736        bu->ic->acc->prpl->add_buddy(bu->ic, bu->handle,
737                                     icc->group ? icc->group->name : NULL);
738
739        return TRUE;
740}
741
742static void control_channel_kick(irc_channel_t *ic, irc_user_t *iu, const char *msg)
743{
744        struct irc_control_channel *icc = ic->data;
745        bee_user_t *bu = iu->bu;
746
747        if (bu == NULL) {
748                return;
749        }
750
751        if (icc->type != IRC_CC_TYPE_GROUP) {
752                irc_send_num(ic->irc, 482, "%s :Kicks are only possible to fill_by=group channels", ic->name);
753                return;
754        }
755
756        bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle,
757                                        icc->group ? icc->group->name : NULL);
758}
759
760static char *set_eval_by_account(set_t *set, char *value);
761static char *set_eval_fill_by(set_t *set, char *value);
762static char *set_eval_by_group(set_t *set, char *value);
763static char *set_eval_by_protocol(set_t *set, char *value);
764static char *set_eval_show_users(set_t *set, char *value);
765
766static gboolean control_channel_init(irc_channel_t *ic)
767{
768        struct irc_control_channel *icc;
769
770        set_add(&ic->set, "account", NULL, set_eval_by_account, ic);
771        set_add(&ic->set, "fill_by", "all", set_eval_fill_by, ic);
772        set_add(&ic->set, "group", NULL, set_eval_by_group, ic);
773        set_add(&ic->set, "protocol", NULL, set_eval_by_protocol, ic);
774
775        /* When changing the default, also change it below. */
776        set_add(&ic->set, "show_users", "online+,special%,away", set_eval_show_users, ic);
777
778        ic->data = icc = g_new0(struct irc_control_channel, 1);
779        icc->type = IRC_CC_TYPE_DEFAULT;
780
781        /* Have to run the evaluator to initialize icc->modes. */
782        set_setstr(&ic->set, "show_users", "online+,special%,away");
783
784        /* For scripts that care. */
785        irc_channel_set_mode(ic, "+C");
786
787        return TRUE;
788}
789
790static gboolean control_channel_join(irc_channel_t *ic)
791{
792        bee_irc_channel_update(ic->irc, ic, NULL);
793
794        return TRUE;
795}
796
797static char *set_eval_by_account(set_t *set, char *value)
798{
799        struct irc_channel *ic = set->data;
800        struct irc_control_channel *icc = ic->data;
801        account_t *acc;
802
803        if (!(acc = account_get(ic->irc->b, value))) {
804                return SET_INVALID;
805        }
806
807        icc->account = acc;
808        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_ACCOUNT) {
809                bee_irc_channel_update(ic->irc, ic, NULL);
810        }
811
812        return g_strdup(acc->tag);
813}
814
815static char *set_eval_fill_by(set_t *set, char *value)
816{
817        struct irc_channel *ic = set->data;
818        struct irc_control_channel *icc = ic->data;
819        char *s;
820
821        icc->type &= ~(IRC_CC_TYPE_MASK | IRC_CC_TYPE_INVERT);
822
823        s = value;
824        if (s[0] == '!') {
825                icc->type |= IRC_CC_TYPE_INVERT;
826                s++;
827        }
828
829        if (strcmp(s, "all") == 0) {
830                icc->type |= IRC_CC_TYPE_DEFAULT;
831        } else if (strcmp(s, "rest") == 0) {
832                icc->type |= IRC_CC_TYPE_REST;
833        } else if (strcmp(s, "group") == 0) {
834                icc->type |= IRC_CC_TYPE_GROUP;
835        } else if (strcmp(s, "account") == 0) {
836                icc->type |= IRC_CC_TYPE_ACCOUNT;
837        } else if (strcmp(s, "protocol") == 0) {
838                icc->type |= IRC_CC_TYPE_PROTOCOL;
839        } else {
840                return SET_INVALID;
841        }
842
843        bee_irc_channel_update(ic->irc, ic, NULL);
844        return value;
845}
846
847static char *set_eval_by_group(set_t *set, char *value)
848{
849        struct irc_channel *ic = set->data;
850        struct irc_control_channel *icc = ic->data;
851
852        icc->group = bee_group_by_name(ic->irc->b, value, TRUE);
853        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_GROUP) {
854                bee_irc_channel_update(ic->irc, ic, NULL);
855        }
856
857        return g_strdup(icc->group->name);
858}
859
860static char *set_eval_by_protocol(set_t *set, char *value)
861{
862        struct irc_channel *ic = set->data;
863        struct irc_control_channel *icc = ic->data;
864        struct prpl *prpl;
865
866        if (!(prpl = find_protocol(value))) {
867                return SET_INVALID;
868        }
869
870        icc->protocol = prpl;
871        if ((icc->type & IRC_CC_TYPE_MASK) == IRC_CC_TYPE_PROTOCOL) {
872                bee_irc_channel_update(ic->irc, ic, NULL);
873        }
874
875        return value;
876}
877
878static char *set_eval_show_users(set_t *set, char *value)
879{
880        struct irc_channel *ic = set->data;
881        struct irc_control_channel *icc = ic->data;
882        char **parts = g_strsplit(value, ",", 0), **part;
883        char modes[5];
884
885        memset(modes, 0, 5);
886        for (part = parts; *part; part++) {
887                char last, modechar = IRC_CHANNEL_USER_NONE;
888
889                if (**part == '\0') {
890                        goto fail;
891                }
892
893                last = (*part)[strlen(*part + 1)];
894                if (last == '+') {
895                        modechar = IRC_CHANNEL_USER_VOICE;
896                } else if (last == '%') {
897                        modechar = IRC_CHANNEL_USER_HALFOP;
898                } else if (last == '@') {
899                        modechar = IRC_CHANNEL_USER_OP;
900                }
901
902                if (strncmp(*part, "offline", 7) == 0) {
903                        modes[0] = modechar;
904                } else if (strncmp(*part, "away", 4) == 0) {
905                        modes[1] = modechar;
906                } else if (strncmp(*part, "special", 7) == 0) {
907                        modes[2] = modechar;
908                } else if (strncmp(*part, "online", 6) == 0) {
909                        modes[3] = modechar;
910                } else {
911                        goto fail;
912                }
913        }
914        memcpy(icc->modes, modes, 5);
915        bee_irc_channel_update(ic->irc, ic, NULL);
916
917        g_strfreev(parts);
918        return value;
919
920fail:
921        g_strfreev(parts);
922        return SET_INVALID;
923}
924
925/* Figure out if a channel is supposed to have the user, assuming s/he is
926   online or otherwise also selected by the show_users setting. Only works
927   for control channels, but does *not* check if this channel is of that
928   type. Beware! */
929gboolean irc_channel_wants_user(irc_channel_t *ic, irc_user_t *iu)
930{
931        struct irc_control_channel *icc = ic->data;
932        gboolean ret = FALSE;
933
934        if (iu->bu == NULL) {
935                return FALSE;
936        }
937
938        switch (icc->type & IRC_CC_TYPE_MASK) {
939        case IRC_CC_TYPE_GROUP:
940                ret = iu->bu->group == icc->group;
941                break;
942        case IRC_CC_TYPE_ACCOUNT:
943                ret = iu->bu->ic->acc == icc->account;
944                break;
945        case IRC_CC_TYPE_PROTOCOL:
946                ret = iu->bu->ic->acc->prpl == icc->protocol;
947                break;
948        case IRC_CC_TYPE_DEFAULT:
949        default:
950                ret = TRUE;
951                break;
952        }
953
954        if (icc->type & IRC_CC_TYPE_INVERT) {
955                ret = !ret;
956        }
957
958        return ret;
959}
960
961static gboolean control_channel_free(irc_channel_t *ic)
962{
963        struct irc_control_channel *icc = ic->data;
964
965        set_del(&ic->set, "account");
966        set_del(&ic->set, "fill_by");
967        set_del(&ic->set, "group");
968        set_del(&ic->set, "protocol");
969        set_del(&ic->set, "show_users");
970
971        g_free(icc);
972        ic->data = NULL;
973
974        /* For scripts that care. */
975        irc_channel_set_mode(ic, "-C");
976
977        return TRUE;
978}
979
980static const struct irc_channel_funcs control_channel_funcs = {
981        control_channel_privmsg,
982        control_channel_join,
983        NULL,
984        NULL,
985        control_channel_invite,
986        control_channel_kick,
987
988        control_channel_init,
989        control_channel_free,
990};
Note: See TracBrowser for help on using the repository browser.