source: irc_channel.c @ f7ca587

Last change on this file since f7ca587 was f7ca587, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-29T18:18:54Z

Restore default_target setting, kill last_root_cmd variable and just use
the last_channel variable, like for any other user.

  • Property mode set to 100644
File size: 16.8 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2010 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., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  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       
38        if( !irc_channel_name_ok( name ) || irc_channel_by_name( irc, name ) )
39                return NULL;
40       
41        ic = g_new0( irc_channel_t, 1 );
42        ic->irc = irc;
43        ic->name = g_strdup( name );
44        strcpy( ic->mode, CMODE );
45       
46        irc_channel_add_user( ic, irc->root );
47       
48        irc->channels = g_slist_append( irc->channels, ic );
49       
50        set_add( &ic->set, "auto_join", "false", set_eval_bool, ic );
51        set_add( &ic->set, "type", "control", set_eval_channel_type, ic );
52       
53        if( name[0] == '&' )
54                set_setstr( &ic->set, "type", "control" );
55        else /* if( name[0] == '#' ) */
56                set_setstr( &ic->set, "type", "chat" );
57       
58        return ic;
59}
60
61irc_channel_t *irc_channel_by_name( irc_t *irc, const char *name )
62{
63        GSList *l;
64       
65        for( l = irc->channels; l; l = l->next )
66        {
67                irc_channel_t *ic = l->data;
68               
69                if( irc_channel_name_cmp( name, ic->name ) == 0 )
70                        return ic;
71        }
72       
73        return NULL;
74}
75
76irc_channel_t *irc_channel_get( irc_t *irc, char *id )
77{
78        irc_channel_t *ic, *ret = NULL;
79        GSList *l;
80        int nr;
81       
82        if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
83        {
84                for( l = irc->channels; l; l = l->next )
85                {
86                        ic = l->data;
87                        if( ( nr-- ) == 0 ) 
88                                return ic;
89                }
90               
91                return NULL;
92        }
93       
94        /* Exact match first: Partial match only sucks if there's a channel
95           #aa and #aabb */
96        if( ( ret = irc_channel_by_name( irc, id ) ) )
97                return ret;
98       
99        for( l = irc->channels; l; l = l->next )
100        {
101                ic = l->data;
102               
103                if( strstr( ic->name, id ) )
104                {
105                        /* Make sure it's a unique match. */
106                        if( !ret )
107                                ret = ic;
108                        else
109                                return NULL;
110                }
111        }
112       
113        return ret;
114}
115
116int irc_channel_free( irc_channel_t *ic )
117{
118        irc_t *irc = ic->irc;
119        GSList *l;
120       
121        if( ic->flags & IRC_CHANNEL_JOINED )
122                irc_channel_del_user( ic, irc->user, IRC_CDU_KICK, "Cleaning up channel" );
123       
124        if( ic->f->_free )
125                ic->f->_free( ic );
126       
127        while( ic->set )
128                set_del( &ic->set, ic->set->key );
129       
130        irc->channels = g_slist_remove( irc->channels, ic );
131        while( ic->users )
132        {
133                g_free( ic->users->data );
134                ic->users = g_slist_remove( ic->users, ic->users->data );
135        }
136       
137        for( l = irc->users; l; l = l->next )
138        {
139                irc_user_t *iu = l->data;
140               
141                if( iu->last_channel == ic )
142                        iu->last_channel = irc->default_channel;
143        }
144       
145        g_free( ic->name );
146        g_free( ic->topic );
147        g_free( ic->topic_who );
148        g_free( ic );
149       
150        return 1;
151}
152
153struct irc_channel_free_data
154{
155        irc_t *irc;
156        irc_channel_t *ic;
157        char *name;
158};
159
160static gboolean irc_channel_free_callback( gpointer data, gint fd, b_input_condition cond )
161{
162        struct irc_channel_free_data *d = data;
163       
164        if( g_slist_find( irc_connection_list, d->irc ) &&
165            irc_channel_by_name( d->irc, d->name ) == d->ic &&
166            !( d->ic->flags & IRC_CHANNEL_JOINED ) )
167                irc_channel_free( d->ic );
168
169        g_free( d->name );
170        g_free( d );
171        return FALSE;
172}
173
174/* Free the channel, but via the event loop, so after finishing whatever event
175   we're currently handling. */
176void irc_channel_free_soon( irc_channel_t *ic )
177{
178        struct irc_channel_free_data *d = g_new0( struct irc_channel_free_data, 1 );
179       
180        d->irc = ic->irc;
181        d->ic = ic;
182        d->name = g_strdup( ic->name );
183       
184        b_timeout_add( 0, irc_channel_free_callback, d );
185}
186
187static char *set_eval_channel_type( set_t *set, char *value )
188{
189        struct irc_channel *ic = set->data;
190        const struct irc_channel_funcs *new;
191       
192        if( strcmp( value, "control" ) == 0 )
193                new = &control_channel_funcs;
194        else if( strcmp( value, "chat" ) == 0 )
195                new = &irc_channel_im_chat_funcs;
196        else
197                return SET_INVALID;
198       
199        /* TODO: Return values. */
200        if( ic->f && ic->f->_free )
201                ic->f->_free( ic );
202       
203        ic->f = new;
204       
205        if( ic->f && ic->f->_init )
206                ic->f->_init( ic );
207       
208        return value;
209}
210
211int irc_channel_add_user( irc_channel_t *ic, irc_user_t *iu )
212{
213        irc_channel_user_t *icu;
214       
215        if( irc_channel_has_user( ic, iu ) )
216                return 0;
217       
218        icu = g_new0( irc_channel_user_t, 1 );
219        icu->iu = iu;
220       
221        ic->users = g_slist_insert_sorted( ic->users, icu, irc_channel_user_cmp );
222       
223        irc_channel_update_ops( ic, set_getstr( &ic->irc->b->set, "ops" ) );
224       
225        if( iu == ic->irc->user || ic->flags & IRC_CHANNEL_JOINED )
226        {
227                ic->flags |= IRC_CHANNEL_JOINED;
228                irc_send_join( ic, iu );
229        }
230       
231        return 1;
232}
233
234int irc_channel_del_user( irc_channel_t *ic, irc_user_t *iu, irc_channel_del_user_type_t type, const char *msg )
235{
236        irc_channel_user_t *icu;
237       
238        if( !( icu = irc_channel_has_user( ic, iu ) ) )
239                return 0;
240       
241        ic->users = g_slist_remove( ic->users, icu );
242        g_free( icu );
243       
244        if( !( ic->flags & IRC_CHANNEL_JOINED ) || type == IRC_CDU_SILENT ) {}
245                /* Do nothing. The caller should promise it won't screw
246                   up state of the IRC client. :-) */
247        else if( type == IRC_CDU_PART )
248                irc_send_part( ic, iu, msg );
249        else if( type == IRC_CDU_KICK )
250                irc_send_kick( ic, iu, ic->irc->root, msg );
251       
252        if( iu == ic->irc->user )
253        {
254                ic->flags &= ~IRC_CHANNEL_JOINED;
255               
256                if( ic->irc->status & USTATUS_SHUTDOWN )
257                {
258                        /* Don't do anything fancy when we're shutting down anyway. */
259                }
260                else if( ic->flags & IRC_CHANNEL_TEMP )
261                {
262                        irc_channel_free_soon( ic );
263                }
264                else
265                {
266                        /* Flush userlist now. The user won't see it anyway. */
267                        while( ic->users )
268                        {
269                                g_free( ic->users->data );
270                                ic->users = g_slist_remove( ic->users, ic->users->data );
271                        }
272                        irc_channel_add_user( ic, ic->irc->root );
273                }
274        }
275       
276        return 1;
277}
278
279irc_channel_user_t *irc_channel_has_user( irc_channel_t *ic, irc_user_t *iu )
280{
281        GSList *l;
282       
283        for( l = ic->users; l; l = l->next )
284        {
285                irc_channel_user_t *icu = l->data;
286               
287                if( icu->iu == iu )
288                        return icu;
289        }
290       
291        return NULL;
292}
293
294int irc_channel_set_topic( irc_channel_t *ic, const char *topic, const irc_user_t *iu )
295{
296        g_free( ic->topic );
297        ic->topic = g_strdup( topic );
298       
299        g_free( ic->topic_who );
300        if( iu )
301                ic->topic_who = g_strdup_printf( "%s!%s@%s", iu->nick, iu->user, iu->host );
302        else
303                ic->topic_who = NULL;
304       
305        ic->topic_time = time( NULL );
306       
307        if( ic->flags & IRC_CHANNEL_JOINED )
308                irc_send_topic( ic, TRUE );
309       
310        return 1;
311}
312
313void irc_channel_user_set_mode( irc_channel_t *ic, irc_user_t *iu, irc_channel_user_flags_t flags )
314{
315        irc_channel_user_t *icu = irc_channel_has_user( ic, iu );
316       
317        if( !icu || icu->flags == flags )
318                return;
319       
320        if( ic->flags & IRC_CHANNEL_JOINED )
321                irc_send_channel_user_mode_diff( ic, iu, icu->flags, flags );
322       
323        icu->flags = flags;
324}
325
326void irc_channel_auto_joins( irc_t *irc, account_t *acc )
327{
328        GSList *l;
329       
330        for( l = irc->channels; l; l = l->next )
331        {
332                irc_channel_t *ic = l->data;
333                gboolean aj = set_getbool( &ic->set, "auto_join" );
334                char *type;
335               
336                if( acc &&
337                    ( type = set_getstr( &ic->set, "chat_type" ) ) &&
338                    strcmp( type, "room" ) == 0 )
339                {
340                        /* Bit of an ugly special case: Handle chatrooms here, we
341                           can only auto-join them if their account is online. */
342                        char *acc_s;
343                       
344                        if( !aj && !( ic->flags & IRC_CHANNEL_JOINED ) )
345                                /* Only continue if this one's marked as auto_join
346                                   or if we're in it already. (Possible if the
347                                   client auto-rejoined it before identyfing.) */
348                                continue;
349                        else if( !( acc_s = set_getstr( &ic->set, "account" ) ) )
350                                continue;
351                        else if( account_get( irc->b, acc_s ) != acc )
352                                continue;
353                        else if( acc->ic == NULL || !( acc->ic->flags & OPT_LOGGED_IN ) )
354                                continue;
355                        else
356                                ic->f->join( ic );
357                }
358                else if( aj )
359                {
360                        irc_channel_add_user( ic, irc->user );
361                }
362        }
363}
364
365void irc_channel_printf( irc_channel_t *ic, char *format, ... )
366{
367        va_list params;
368        char *text;
369       
370        va_start( params, format );
371        text = g_strdup_vprintf( format, params );
372        va_end( params );
373       
374        irc_send_msg( ic->irc->root, "PRIVMSG", ic->name, text, NULL );
375        g_free( text );
376}
377
378gboolean irc_channel_name_ok( const char *name_ )
379{
380        const unsigned char *name = (unsigned char*) name_;
381        int i;
382       
383        if( name_[0] == '\0' )
384                return FALSE;
385       
386        /* Check if the first character is in CTYPES (#&) */
387        if( strchr( CTYPES, name_[0] ) == NULL )
388                return FALSE;
389       
390        /* RFC 1459 keeps amazing me: While only a "few" chars are allowed
391           in nicknames, channel names can be pretty much anything as long
392           as they start with # or &. I'll be a little bit more strict and
393           disallow all non-printable characters. */
394        for( i = 1; name[i]; i ++ )
395                if( name[i] <= ' ' || name[i] == ',' )
396                        return FALSE;
397       
398        return TRUE;
399}
400
401void irc_channel_name_strip( char *name )
402{
403        int i, j;
404       
405        for( i = j = 0; name[i]; i ++ )
406                if( name[i] > ' ' && name[i] != ',' )
407                        name[j++] = name[i];
408       
409        name[j] = '\0';
410}
411
412int irc_channel_name_cmp( const char *a_, const char *b_ )
413{
414        static unsigned char case_map[256];
415        const unsigned char *a = (unsigned char*) a_, *b = (unsigned char*) b_;
416        int i;
417       
418        if( case_map['A'] == '\0' )
419        {
420                for( i = 33; i < 256; i ++ )
421                        if( i != ',' )
422                                case_map[i] = i;
423               
424                for( i = 0; i < 26; i ++ )
425                        case_map['A'+i] = 'a' + i;
426               
427                case_map['['] = '{';
428                case_map[']'] = '}';
429                case_map['~'] = '`';
430                case_map['\\'] = '|';
431        }
432       
433        if( !irc_channel_name_ok( a_ ) || !irc_channel_name_ok( b_ ) )
434                return -1;
435       
436        for( i = 0; a[i] && b[i] && case_map[a[i]] && case_map[b[i]]; i ++ )
437        {
438                if( case_map[a[i]] == case_map[b[i]] )
439                        continue;
440                else
441                        return case_map[a[i]] - case_map[b[i]];
442        }
443       
444        return case_map[a[i]] - case_map[b[i]];
445}
446
447static gint irc_channel_user_cmp( gconstpointer a_, gconstpointer b_ )
448{
449        const irc_channel_user_t *a = a_, *b = b_;
450       
451        return irc_user_cmp( a->iu, b->iu );
452}
453
454void irc_channel_update_ops( irc_channel_t *ic, char *value )
455{
456        irc_channel_user_set_mode( ic, ic->irc->root,
457                ( strcmp( value, "both" ) == 0 ||
458                  strcmp( value, "root" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 );
459        irc_channel_user_set_mode( ic, ic->irc->user,
460                ( strcmp( value, "both" ) == 0 ||
461                  strcmp( value, "user" ) == 0 ) ? IRC_CHANNEL_USER_OP : 0 );
462}
463
464char *set_eval_irc_channel_ops( set_t *set, char *value )
465{
466        irc_t *irc = set->data;
467        GSList *l;
468       
469        if( strcmp( value, "both" ) != 0 && strcmp( value, "none" ) != 0 && 
470            strcmp( value, "user" ) != 0 && strcmp( value, "root" ) != 0 )
471                return SET_INVALID;
472       
473        for( l = irc->channels; l; l = l->next )
474                irc_channel_update_ops( l->data, value );
475       
476        return value;
477}
478
479/* Channel-type dependent functions, for control channels: */
480static gboolean control_channel_privmsg( irc_channel_t *ic, const char *msg )
481{
482        irc_t *irc = ic->irc;
483        irc_user_t *iu;
484        const char *s;
485       
486        /* Scan for non-whitespace chars followed by a colon: */
487        for( s = msg; *s && !isspace( *s ) && *s != ':' && *s != ','; s ++ ) {}
488       
489        if( *s == ':' || *s == ',' )
490        {
491                char to[s-msg+1];
492               
493                memset( to, 0, sizeof( to ) );
494                strncpy( to, msg, s - msg );
495                while( *(++s) && isspace( *s ) ) {}
496                msg = s;
497               
498                if( !( iu = irc_user_by_name( irc, to ) ) )
499                        irc_channel_printf( ic, "User does not exist: %s", to );
500                else
501                        ic->last_target = iu;
502        }
503        else if( g_strcasecmp( set_getstr( &irc->b->set, "default_target" ), "last" ) == 0 &&
504                 ic->last_target && g_slist_find( irc->users, ic->last_target ) )
505                iu = ic->last_target;
506        else
507                iu = irc->root;
508       
509        if( iu && iu->f->privmsg )
510        {
511                iu->last_channel = ic;
512                iu->f->privmsg( iu, msg );
513        }
514       
515        return TRUE;
516}
517
518static gboolean control_channel_invite( irc_channel_t *ic, irc_user_t *iu )
519{
520        struct irc_control_channel *icc = ic->data;
521        bee_user_t *bu = iu->bu;
522       
523        if( bu == NULL )
524                return FALSE;
525       
526        if( icc->type != IRC_CC_TYPE_GROUP )
527        {
528                irc_send_num( ic->irc, 482, "%s :Invitations are only possible to fill_by=group channels", ic->name );
529                return FALSE;
530        }
531       
532        bu->ic->acc->prpl->add_buddy( bu->ic, bu->handle,
533                                      icc->group ? icc->group->name : NULL );
534       
535        return TRUE;
536}
537
538static char *set_eval_by_account( set_t *set, char *value );
539static char *set_eval_fill_by( set_t *set, char *value );
540static char *set_eval_by_group( set_t *set, char *value );
541static char *set_eval_by_protocol( set_t *set, char *value );
542static char *set_eval_show_users( set_t *set, char *value );
543
544static gboolean control_channel_init( irc_channel_t *ic )
545{
546        struct irc_control_channel *icc;
547       
548        set_add( &ic->set, "account", NULL, set_eval_by_account, ic );
549        set_add( &ic->set, "fill_by", "all", set_eval_fill_by, ic );
550        set_add( &ic->set, "group", NULL, set_eval_by_group, ic );
551        set_add( &ic->set, "protocol", NULL, set_eval_by_protocol, ic );
552       
553        /* When changing the default, also change it below. */
554        set_add( &ic->set, "show_users", "online+,away", set_eval_show_users, ic );
555       
556        ic->data = icc = g_new0( struct irc_control_channel, 1 );
557        icc->type = IRC_CC_TYPE_DEFAULT;
558       
559        /* Have to run the evaluator to initialize icc->modes. */
560        set_setstr( &ic->set, "show_users", "online+,away" );
561       
562        return TRUE;
563}
564
565static gboolean control_channel_join( irc_channel_t *ic )
566{
567        bee_irc_channel_update( ic->irc, ic, NULL );
568       
569        return TRUE;
570}
571
572static char *set_eval_by_account( set_t *set, char *value )
573{
574        struct irc_channel *ic = set->data;
575        struct irc_control_channel *icc = ic->data;
576        account_t *acc;
577       
578        if( !( acc = account_get( ic->irc->b, value ) ) )
579                return SET_INVALID;
580       
581        icc->account = acc;
582        if( icc->type == IRC_CC_TYPE_ACCOUNT )
583                bee_irc_channel_update( ic->irc, ic, NULL );
584       
585        return g_strdup( acc->tag );
586}
587
588static char *set_eval_fill_by( set_t *set, char *value )
589{
590        struct irc_channel *ic = set->data;
591        struct irc_control_channel *icc = ic->data;
592       
593        if( strcmp( value, "all" ) == 0 )
594                icc->type = IRC_CC_TYPE_DEFAULT;
595        else if( strcmp( value, "rest" ) == 0 )
596                icc->type = IRC_CC_TYPE_REST;
597        else if( strcmp( value, "group" ) == 0 )
598                icc->type = IRC_CC_TYPE_GROUP;
599        else if( strcmp( value, "account" ) == 0 )
600                icc->type = IRC_CC_TYPE_ACCOUNT;
601        else if( strcmp( value, "protocol" ) == 0 )
602                icc->type = IRC_CC_TYPE_PROTOCOL;
603        else
604                return SET_INVALID;
605       
606        bee_irc_channel_update( ic->irc, ic, NULL );
607        return value;
608}
609
610static char *set_eval_by_group( set_t *set, char *value )
611{
612        struct irc_channel *ic = set->data;
613        struct irc_control_channel *icc = ic->data;
614       
615        icc->group = bee_group_by_name( ic->irc->b, value, TRUE );
616        if( icc->type == IRC_CC_TYPE_GROUP )
617                bee_irc_channel_update( ic->irc, ic, NULL );
618       
619        return g_strdup( icc->group->name );
620}
621
622static char *set_eval_by_protocol( set_t *set, char *value )
623{
624        struct irc_channel *ic = set->data;
625        struct irc_control_channel *icc = ic->data;
626        struct prpl *prpl;
627       
628        if( !( prpl = find_protocol( value ) ) )
629                return SET_INVALID;
630       
631        icc->protocol = prpl;
632        if( icc->type == IRC_CC_TYPE_PROTOCOL )
633                bee_irc_channel_update( ic->irc, ic, NULL );
634       
635        return value;
636}
637
638static char *set_eval_show_users( set_t *set, char *value )
639{
640        struct irc_channel *ic = set->data;
641        struct irc_control_channel *icc = ic->data;
642        char **parts = g_strsplit( value, ",", 0 ), **part;
643        char modes[4];
644       
645        memset( modes, 0, 4 );
646        for( part = parts; *part; part ++ )
647        {
648                char last, modechar = IRC_CHANNEL_USER_NONE;
649               
650                if( **part == '\0' )
651                        goto fail;
652               
653                last = (*part)[strlen(*part+1)];
654                if( last == '+' )
655                        modechar = IRC_CHANNEL_USER_VOICE;
656                else if( last == '%' )
657                        modechar = IRC_CHANNEL_USER_HALFOP;
658                else if( last == '@' )
659                        modechar = IRC_CHANNEL_USER_OP;
660               
661                if( strncmp( *part, "offline", 7 ) == 0 )
662                        modes[0] = modechar;
663                else if( strncmp( *part, "away", 4 ) == 0 )
664                        modes[1] = modechar;
665                else if( strncmp( *part, "online", 6 ) == 0 )
666                        modes[2] = modechar;
667                else
668                        goto fail;
669        }
670        memcpy( icc->modes, modes, 4 );
671        bee_irc_channel_update( ic->irc, ic, NULL );
672       
673        g_strfreev( parts );
674        return value;
675       
676fail:
677        g_strfreev( parts );
678        return SET_INVALID;     
679}
680
681static gboolean control_channel_free( irc_channel_t *ic )
682{
683        struct irc_control_channel *icc = ic->data;
684       
685        set_del( &ic->set, "account" );
686        set_del( &ic->set, "fill_by" );
687        set_del( &ic->set, "group" );
688        set_del( &ic->set, "protocol" );
689       
690        g_free( icc );
691        ic->data = NULL;
692       
693        return TRUE;
694}
695
696static const struct irc_channel_funcs control_channel_funcs = {
697        control_channel_privmsg,
698        control_channel_join,
699        NULL,
700        NULL,
701        control_channel_invite,
702       
703        control_channel_init,
704        control_channel_free,
705};
Note: See TracBrowser for help on using the repository browser.