source: protocols/nogaim.c @ 7b8238d

Last change on this file since 7b8238d was 11e7828, checked in by dequis <dx@…>, at 2015-01-26T02:43:35Z

Fix whatsapp local contact lists

Had to move the code that adds contacts to imcb_connected to avoid
dereferencing a null im_connection.

Turns out this kind of local contact lists only applies to renamed
contacts, though. It doesn't deal with libpurple's blist.xml at all
(it could, there are APIs for it since 2.6.0)

  • Property mode set to 100644
File size: 17.9 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/*
8 * nogaim
9 *
10 * Gaim without gaim - for BitlBee
11 *
12 * This file contains functions called by the Gaim IM-modules. It's written
13 * from scratch for BitlBee and doesn't contain any code from Gaim anymore
14 * (except for the function names).
15 */
16
17/*
18  This program is free software; you can redistribute it and/or modify
19  it under the terms of the GNU General Public License as published by
20  the Free Software Foundation; either version 2 of the License, or
21  (at your option) any later version.
22
23  This program is distributed in the hope that it will be useful,
24  but WITHOUT ANY WARRANTY; without even the implied warranty of
25  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26  GNU General Public License for more details.
27
28  You should have received a copy of the GNU General Public License with
29  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
30  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
31  Fifth Floor, Boston, MA  02110-1301  USA
32*/
33
34#define BITLBEE_CORE
35#include <ctype.h>
36
37#include "nogaim.h"
38
39GSList *connections;
40
41#ifdef WITH_PLUGINS
42gboolean load_plugin(char *path)
43{
44        void (*init_function) (void);
45       
46        GModule *mod = g_module_open(path, G_MODULE_BIND_LAZY);
47
48        if(!mod) {
49                log_message(LOGLVL_ERROR, "Can't find `%s', not loading (%s)\n", path, g_module_error());
50                return FALSE;
51        }
52
53        if(!g_module_symbol(mod,"init_plugin",(gpointer *) &init_function)) {
54                log_message(LOGLVL_WARNING, "Can't find function `init_plugin' in `%s'\n", path);
55                return FALSE;
56        }
57
58        init_function();
59
60        return TRUE;
61}
62
63void load_plugins(void)
64{
65        GDir *dir;
66        GError *error = NULL;
67
68        dir = g_dir_open(global.conf->plugindir, 0, &error);
69
70        if (dir) {
71                const gchar *entry;
72                char *path;
73
74                while ((entry = g_dir_read_name(dir))) {
75                        path = g_build_filename(global.conf->plugindir, entry, NULL);
76                        if(!path) {
77                                log_message(LOGLVL_WARNING, "Can't build path for %s\n", entry);
78                                continue;
79                        }
80
81                        load_plugin(path);
82
83                        g_free(path);
84                }
85
86                g_dir_close(dir);
87        }
88}
89#endif
90
91GList *protocols = NULL;
92 
93void register_protocol (struct prpl *p)
94{
95        int i;
96        gboolean refused = global.conf->protocols != NULL;
97 
98        for (i = 0; global.conf->protocols && global.conf->protocols[i]; i++)
99        {
100                if (g_strcasecmp(p->name, global.conf->protocols[i]) == 0)
101                        refused = FALSE;
102        }
103
104        if (refused)
105                log_message(LOGLVL_WARNING, "Protocol %s disabled\n", p->name);
106        else
107                protocols = g_list_append(protocols, p);
108}
109
110struct prpl *find_protocol(const char *name)
111{
112        GList *gl;
113       
114        for( gl = protocols; gl; gl = gl->next )
115        {
116                struct prpl *proto = gl->data;
117               
118                if( g_strcasecmp( proto->name, name ) == 0 )
119                        return proto;
120        }
121       
122        return NULL;
123}
124
125void nogaim_init()
126{
127        extern void msn_initmodule();
128        extern void oscar_initmodule();
129        extern void byahoo_initmodule();
130        extern void jabber_initmodule();
131        extern void twitter_initmodule();
132        extern void purple_initmodule();
133
134#ifdef WITH_MSN
135        msn_initmodule();
136#endif
137
138#ifdef WITH_OSCAR
139        oscar_initmodule();
140#endif
141       
142#ifdef WITH_YAHOO
143        byahoo_initmodule();
144#endif
145       
146#ifdef WITH_JABBER
147        jabber_initmodule();
148#endif
149
150#ifdef WITH_TWITTER
151        twitter_initmodule();
152#endif
153
154#ifdef WITH_PURPLE
155        purple_initmodule();
156#endif
157
158#ifdef WITH_PLUGINS
159        load_plugins();
160#endif
161}
162
163GSList *get_connections() { return connections; }
164
165struct im_connection *imcb_new( account_t *acc )
166{
167        struct im_connection *ic;
168       
169        ic = g_new0( struct im_connection, 1 );
170       
171        ic->bee = acc->bee;
172        ic->acc = acc;
173        acc->ic = ic;
174       
175        connections = g_slist_append( connections, ic );
176       
177        return( ic );
178}
179
180void imc_free( struct im_connection *ic )
181{
182        account_t *a;
183       
184        /* Destroy the pointer to this connection from the account list */
185        for( a = ic->bee->accounts; a; a = a->next )
186                if( a->ic == ic )
187                {
188                        a->ic = NULL;
189                        break;
190                }
191       
192        connections = g_slist_remove( connections, ic );
193        g_free( ic );
194}
195
196static void serv_got_crap( struct im_connection *ic, char *format, ... )
197{
198        va_list params;
199        char *text;
200        account_t *a;
201       
202        va_start( params, format );
203        text = g_strdup_vprintf( format, params );
204        va_end( params );
205
206        if( ( g_strcasecmp( set_getstr( &ic->bee->set, "strip_html" ), "always" ) == 0 ) ||
207            ( ( ic->flags & OPT_DOES_HTML ) && set_getbool( &ic->bee->set, "strip_html" ) ) )
208                strip_html( text );
209       
210        /* Try to find a different connection on the same protocol. */
211        for( a = ic->bee->accounts; a; a = a->next )
212                if( a->prpl == ic->acc->prpl && a->ic != ic )
213                        break;
214       
215        /* If we found one, include the screenname in the message. */
216        if( a )
217                /* FIXME(wilmer): ui_log callback or so */
218                irc_rootmsg( ic->bee->ui_data, "%s - %s", ic->acc->tag, text );
219        else
220                irc_rootmsg( ic->bee->ui_data, "%s - %s", ic->acc->prpl->name, text );
221       
222        g_free( text );
223}
224
225void imcb_log( struct im_connection *ic, char *format, ... )
226{
227        va_list params;
228        char *text;
229       
230        va_start( params, format );
231        text = g_strdup_vprintf( format, params );
232        va_end( params );
233       
234        if( ic->flags & OPT_LOGGED_IN )
235                serv_got_crap( ic, "%s", text );
236        else
237                serv_got_crap( ic, "Logging in: %s", text );
238       
239        g_free( text );
240}
241
242void imcb_error( struct im_connection *ic, char *format, ... )
243{
244        va_list params;
245        char *text;
246       
247        va_start( params, format );
248        text = g_strdup_vprintf( format, params );
249        va_end( params );
250       
251        if( ic->flags & OPT_LOGGED_IN )
252                serv_got_crap( ic, "Error: %s", text );
253        else
254                serv_got_crap( ic, "Login error: %s", text );
255       
256        g_free( text );
257}
258
259static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond )
260{
261        struct im_connection *ic = d;
262       
263        if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) )
264        {
265                /* This protocol is expected to ack keepalives and hasn't
266                   since the last time we were here. */
267                imcb_error( ic, "Connection timeout" );
268                imc_logout( ic, TRUE );
269                return FALSE;
270        }
271        ic->flags &= ~OPT_PONGED;
272       
273        if( ic->acc->prpl->keepalive )
274                ic->acc->prpl->keepalive( ic );
275       
276        return TRUE;
277}
278
279void start_keepalives( struct im_connection *ic, int interval )
280{
281        b_event_remove( ic->keepalive );
282        ic->keepalive = b_timeout_add( interval, send_keepalive, ic );
283       
284        /* Connecting successfully counts as a first successful pong. */
285        if( ic->flags & OPT_PONGS )
286                ic->flags |= OPT_PONGED;
287}
288
289void imcb_connected( struct im_connection *ic )
290{
291        /* MSN servers sometimes redirect you to a different server and do
292           the whole login sequence again, so these "late" calls to this
293           function should be handled correctly. (IOW, ignored) */
294        if( ic->flags & OPT_LOGGED_IN )
295                return;
296
297        if( ic->acc->flags & ACC_FLAG_LOCAL )
298        {
299                GHashTableIter nicks;
300                gpointer k, v;
301                g_hash_table_iter_init( &nicks, ic->acc->nicks );
302                while( g_hash_table_iter_next( &nicks, &k, &v ) )
303                {
304                        ic->acc->prpl->add_buddy( ic, (char*) k, NULL );
305                }
306        }
307       
308        imcb_log( ic, "Logged in" );
309       
310        ic->flags |= OPT_LOGGED_IN;
311        start_keepalives( ic, 60000 );
312       
313        /* Necessary to send initial presence status, even if we're not away. */
314        imc_away_send_update( ic );
315       
316        /* Apparently we're connected successfully, so reset the
317           exponential backoff timer. */
318        ic->acc->auto_reconnect_delay = 0;
319       
320        if( ic->bee->ui->imc_connected )
321                ic->bee->ui->imc_connected( ic );
322}
323
324gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond )
325{
326        account_t *a = data;
327       
328        a->reconnect = 0;
329        account_on( a->bee, a );
330       
331        return( FALSE );        /* Only have to run the timeout once */
332}
333
334void cancel_auto_reconnect( account_t *a )
335{
336        b_event_remove( a->reconnect );
337        a->reconnect = 0;
338}
339
340void imc_logout( struct im_connection *ic, int allow_reconnect )
341{
342        bee_t *bee = ic->bee;
343        account_t *a;
344        GSList *l;
345        int delay;
346       
347        /* Nested calls might happen sometimes, this is probably the best
348           place to catch them. */
349        if( ic->flags & OPT_LOGGING_OUT )
350                return;
351        else
352                ic->flags |= OPT_LOGGING_OUT;
353       
354        if( ic->bee->ui->imc_disconnected )
355                ic->bee->ui->imc_disconnected( ic );
356       
357        imcb_log( ic, "Signing off.." );
358       
359        /* TBH I don't remember anymore why I didn't just use ic->acc... */
360        for( a = bee->accounts; a; a = a->next )
361                if( a->ic == ic )
362                        break;
363       
364        if( a && !allow_reconnect && !( ic->flags & OPT_LOGGED_IN ) &&
365            set_getbool( &a->set, "oauth" ) )
366        {
367                /* If this account supports OAuth, we're not logged in yet and
368                   not allowed to retry, assume there were auth issues. Give a
369                   helpful message on what might be necessary to fix this. */
370                imcb_log( ic, "If you're having problems logging in, try re-requesting "
371                          "an OAuth token: account %s set password \"\"", a->tag );
372        }
373       
374        for( l = bee->users; l; )
375        {
376                bee_user_t *bu = l->data;
377                GSList *next = l->next;
378               
379                if( bu->ic == ic )
380                        bee_user_free( bee, bu );
381               
382                l = next;
383        }
384       
385        b_event_remove( ic->keepalive );
386        ic->keepalive = 0;
387        ic->acc->prpl->logout( ic );
388        b_event_remove( ic->inpa );
389       
390        g_free( ic->away );
391        ic->away = NULL;
392       
393        query_del_by_conn( (irc_t*) ic->bee->ui_data, ic );
394       
395        if( !a )
396        {
397                /* Uhm... This is very sick. */
398        }
399        else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) &&
400                 set_getbool( &a->set, "auto_reconnect" ) &&
401                 ( delay = account_reconnect_delay( a ) ) > 0 )
402        {
403                imcb_log( ic, "Reconnecting in %d seconds..", delay );
404                a->reconnect = b_timeout_add( delay * 1000, auto_reconnect, a );
405        }
406       
407        imc_free( ic );
408}
409
410void imcb_ask( struct im_connection *ic, char *msg, void *data,
411               query_callback doit, query_callback dont )
412{
413        query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data );
414}
415
416void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data,
417                         query_callback doit, query_callback dont, query_callback myfree )
418{
419        query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data );
420}
421
422void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group )
423{
424        bee_user_t *bu;
425        bee_t *bee = ic->bee;
426        bee_group_t *oldg;
427       
428        if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
429                bu = bee_user_new( bee, ic, handle, 0 );
430       
431        oldg = bu->group;
432        bu->group = bee_group_by_name( bee, group, TRUE );
433       
434        if( bee->ui->user_group && bu->group != oldg )
435                bee->ui->user_group( bee, bu );
436}
437
438void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname )
439{
440        bee_t *bee = ic->bee;
441        bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
442       
443        if( !bu || !fullname ) return;
444       
445        if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 )
446        {
447                g_free( bu->fullname );
448                bu->fullname = g_strdup( fullname );
449               
450                if( bee->ui->user_fullname )
451                        bee->ui->user_fullname( bee, bu );
452        }
453}
454
455void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group )
456{
457        bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) );
458}
459
460/* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM
461   modules to suggest a nickname for a handle. */
462void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick )
463{
464        bee_t *bee = ic->bee;
465        bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
466       
467        if( !bu || !nick ) return;
468       
469        g_free( bu->nick );
470        bu->nick = g_strdup( nick );
471       
472        if( bee->ui->user_nick_hint )
473                bee->ui->user_nick_hint( bee, bu, nick );
474}
475
476
477struct imcb_ask_cb_data
478{
479        struct im_connection *ic;
480        char *handle;
481};
482
483static void imcb_ask_auth_cb_no( void *data )
484{
485        struct imcb_ask_cb_data *cbd = data;
486       
487        cbd->ic->acc->prpl->auth_deny( cbd->ic, cbd->handle );
488       
489        g_free( cbd->handle );
490        g_free( cbd );
491}
492
493static void imcb_ask_auth_cb_yes( void *data )
494{
495        struct imcb_ask_cb_data *cbd = data;
496       
497        cbd->ic->acc->prpl->auth_allow( cbd->ic, cbd->handle );
498       
499        g_free( cbd->handle );
500        g_free( cbd );
501}
502
503void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname )
504{
505        struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 );
506        char *s, *realname_ = NULL;
507       
508        if( realname != NULL )
509                realname_ = g_strdup_printf( " (%s)", realname );
510       
511        s = g_strdup_printf( "The user %s%s wants to add you to his/her buddy list.",
512                             handle, realname_ ? realname_ : "" );
513       
514        g_free( realname_ );
515       
516        data->ic = ic;
517        data->handle = g_strdup( handle );
518        query_add( (irc_t *) ic->bee->ui_data, ic, s,
519                   imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data );
520}
521
522
523static void imcb_ask_add_cb_no( void *data )
524{
525        g_free( ((struct imcb_ask_cb_data*)data)->handle );
526        g_free( data );
527}
528
529static void imcb_ask_add_cb_yes( void *data )
530{
531        struct imcb_ask_cb_data *cbd = data;
532       
533        cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL );
534       
535        imcb_ask_add_cb_no( data );
536}
537
538void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname )
539{
540        struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 );
541        char *s;
542       
543        /* TODO: Make a setting for this! */
544        if( bee_user_by_handle( ic->bee, ic, handle ) != NULL )
545                return;
546       
547        s = g_strdup_printf( "The user %s is not in your buddy list yet. Do you want to add him/her now?", handle );
548       
549        data->ic = ic;
550        data->handle = g_strdup( handle );
551        query_add( (irc_t *) ic->bee->ui_data, ic, s,
552                   imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data );
553}
554
555struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle )
556{
557        return bee_user_by_handle( ic->bee, ic, handle );
558}
559
560/* The plan is to not allow straight calls to prpl functions anymore, but do
561   them all from some wrappers. We'll start to define some down here: */
562
563int imc_chat_msg( struct groupchat *c, char *msg, int flags )
564{
565        char *buf = NULL;
566       
567        if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
568        {
569                buf = escape_html( msg );
570                msg = buf;
571        }
572       
573        c->ic->acc->prpl->chat_msg( c, msg, flags );
574        g_free( buf );
575       
576        return 1;
577}
578
579static char *imc_away_state_find( GList *gcm, char *away, char **message );
580
581int imc_away_send_update( struct im_connection *ic )
582{
583        char *away, *msg = NULL;
584       
585        if( ic->acc->prpl->away_states == NULL ||
586            ic->acc->prpl->set_away == NULL )
587                return 0;
588       
589        away = set_getstr( &ic->acc->set, "away" ) ?
590             : set_getstr( &ic->bee->set, "away" );
591        if( away && *away )
592        {
593                GList *m = ic->acc->prpl->away_states( ic );
594                msg = ic->acc->flags & ACC_FLAG_AWAY_MESSAGE ? away : NULL;
595                away = imc_away_state_find( m, away, &msg ) ? : m->data;
596        }
597        else if( ic->acc->flags & ACC_FLAG_STATUS_MESSAGE )
598        {
599                away = NULL;
600                msg = set_getstr( &ic->acc->set, "status" ) ?
601                    : set_getstr( &ic->bee->set, "status" );
602        }
603       
604        ic->acc->prpl->set_away( ic, away, msg );
605       
606        return 1;
607}
608
609static char *imc_away_alias_list[8][5] =
610{
611        { "Away from computer", "Away", "Extended away", NULL },
612        { "NA", "N/A", "Not available", NULL },
613        { "Busy", "Do not disturb", "DND", "Occupied", NULL },
614        { "Be right back", "BRB", NULL },
615        { "On the phone", "Phone", "On phone", NULL },
616        { "Out to lunch", "Lunch", "Food", NULL },
617        { "Invisible", "Hidden" },
618        { NULL }
619};
620
621static char *imc_away_state_find( GList *gcm, char *away, char **message )
622{
623        GList *m;
624        int i, j;
625       
626        for( m = gcm; m; m = m->next )
627                if( g_strncasecmp( m->data, away, strlen( m->data ) ) == 0 )
628                {
629                        /* At least the Yahoo! module works better if message
630                           contains no data unless it adds something to what
631                           we have in state already. */
632                        if( strlen( m->data ) == strlen( away ) )
633                                *message = NULL;
634                       
635                        return m->data;
636                }
637       
638        for( i = 0; *imc_away_alias_list[i]; i ++ )
639        {
640                int keep_message;
641               
642                for( j = 0; imc_away_alias_list[i][j]; j ++ )
643                        if( g_strncasecmp( away, imc_away_alias_list[i][j], strlen( imc_away_alias_list[i][j] ) ) == 0 )
644                        {
645                                keep_message = strlen( away ) != strlen( imc_away_alias_list[i][j] );
646                                break;
647                        }
648               
649                if( !imc_away_alias_list[i][j] )        /* If we reach the end, this row */
650                        continue;                       /* is not what we want. Next!    */
651               
652                /* Now find an entry in this row which exists in gcm */
653                for( j = 0; imc_away_alias_list[i][j]; j ++ )
654                {
655                        for( m = gcm; m; m = m->next )
656                                if( g_strcasecmp( imc_away_alias_list[i][j], m->data ) == 0 )
657                                {
658                                        if( !keep_message )
659                                                *message = NULL;
660                                       
661                                        return imc_away_alias_list[i][j];
662                                }
663                }
664               
665                /* No need to look further, apparently this state doesn't
666                   have any good alias for this protocol. */
667                break;
668        }
669       
670        return NULL;
671}
672
673void imc_add_allow( struct im_connection *ic, char *handle )
674{
675        if( g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL )
676        {
677                ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) );
678        }
679       
680        ic->acc->prpl->add_permit( ic, handle );
681}
682
683void imc_rem_allow( struct im_connection *ic, char *handle )
684{
685        GSList *l;
686       
687        if( ( l = g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) )
688        {
689                g_free( l->data );
690                ic->permit = g_slist_delete_link( ic->permit, l );
691        }
692       
693        ic->acc->prpl->rem_permit( ic, handle );
694}
695
696void imc_add_block( struct im_connection *ic, char *handle )
697{
698        if( g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL )
699        {
700                ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) );
701        }
702       
703        ic->acc->prpl->add_deny( ic, handle );
704}
705
706void imc_rem_block( struct im_connection *ic, char *handle )
707{
708        GSList *l;
709       
710        if( ( l = g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) )
711        {
712                g_free( l->data );
713                ic->deny = g_slist_delete_link( ic->deny, l );
714        }
715       
716        ic->acc->prpl->rem_deny( ic, handle );
717}
718
719void imcb_clean_handle( struct im_connection *ic, char *handle )
720{
721        /* Accepts a handle and does whatever is necessary to make it
722           BitlBee-friendly. Currently this means removing everything
723           outside 33-127 (ASCII printable excl spaces), @ (only one
724           is allowed) and ! and : */
725        char out[strlen(handle)+1];
726        int s, d;
727       
728        s = d = 0;
729        while( handle[s] )
730        {
731                if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' &&
732                    ( handle[s] & 0x80 ) == 0 )
733                {
734                        if( handle[s] == '@' )
735                        {
736                                /* See if we got an @ already? */
737                                out[d] = 0;
738                                if( strchr( out, '@' ) )
739                                        continue;
740                        }
741                       
742                        out[d++] = handle[s];
743                }
744                s ++;
745        }
746        out[d] = handle[s];
747       
748        strcpy( handle, out );
749}
Note: See TracBrowser for help on using the repository browser.