source: protocols/nogaim.c @ 2370ec2

Last change on this file since 2370ec2 was 748bcdd, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-11-21T15:23:54Z

Time out if logging in takes too long (2m for now). Except for Twitter
OAuth login, which requires user action. This mostly solves problems with
OSCAR login silently failing, but may also be useful in other places.

  • Property mode set to 100644
File size: 16.6 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/*
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., 59 Temple Place,
31  Suite 330, Boston, MA  02111-1307  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_usermsg( ic->bee->ui_data, "%s(%s) - %s", ic->acc->prpl->name, ic->acc->user, text );
219        else
220                irc_usermsg( 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->acc->prpl->keepalive )
264                ic->acc->prpl->keepalive( ic );
265       
266        return TRUE;
267}
268
269void imcb_connected( struct im_connection *ic )
270{
271        /* MSN servers sometimes redirect you to a different server and do
272           the whole login sequence again, so these "late" calls to this
273           function should be handled correctly. (IOW, ignored) */
274        if( ic->flags & OPT_LOGGED_IN )
275                return;
276       
277        imcb_log( ic, "Logged in" );
278       
279        b_event_remove( ic->keepalive );
280        ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );
281        ic->flags |= OPT_LOGGED_IN;
282       
283        /* Necessary to send initial presence status, even if we're not away. */
284        imc_away_send_update( ic );
285       
286        /* Apparently we're connected successfully, so reset the
287           exponential backoff timer. */
288        ic->acc->auto_reconnect_delay = 0;
289       
290        if( ic->bee->ui->imc_connected )
291                ic->bee->ui->imc_connected( ic );
292}
293
294gboolean auto_reconnect( gpointer data, gint fd, b_input_condition cond )
295{
296        account_t *a = data;
297       
298        a->reconnect = 0;
299        account_on( a->bee, a );
300       
301        return( FALSE );        /* Only have to run the timeout once */
302}
303
304void cancel_auto_reconnect( account_t *a )
305{
306        b_event_remove( a->reconnect );
307        a->reconnect = 0;
308}
309
310void imc_logout( struct im_connection *ic, int allow_reconnect )
311{
312        bee_t *bee = ic->bee;
313        account_t *a;
314        GSList *l;
315        int delay;
316       
317        /* Nested calls might happen sometimes, this is probably the best
318           place to catch them. */
319        if( ic->flags & OPT_LOGGING_OUT )
320                return;
321        else
322                ic->flags |= OPT_LOGGING_OUT;
323       
324        if( ic->bee->ui->imc_disconnected )
325                ic->bee->ui->imc_disconnected( ic );
326       
327        imcb_log( ic, "Signing off.." );
328       
329        for( l = bee->users; l; )
330        {
331                bee_user_t *bu = l->data;
332                GSList *next = l->next;
333               
334                if( bu->ic == ic )
335                        bee_user_free( bee, bu );
336               
337                l = next;
338        }
339       
340        b_event_remove( ic->keepalive );
341        ic->keepalive = 0;
342        ic->acc->prpl->logout( ic );
343        b_event_remove( ic->inpa );
344       
345        g_free( ic->away );
346        ic->away = NULL;
347       
348        query_del_by_conn( (irc_t*) ic->bee->ui_data, ic );
349       
350        for( a = bee->accounts; a; a = a->next )
351                if( a->ic == ic )
352                        break;
353       
354        if( !a )
355        {
356                /* Uhm... This is very sick. */
357        }
358        else if( allow_reconnect && set_getbool( &bee->set, "auto_reconnect" ) &&
359                 set_getbool( &a->set, "auto_reconnect" ) &&
360                 ( delay = account_reconnect_delay( a ) ) > 0 )
361        {
362                imcb_log( ic, "Reconnecting in %d seconds..", delay );
363                a->reconnect = b_timeout_add( delay * 1000, auto_reconnect, a );
364        }
365       
366        imc_free( ic );
367}
368
369void imcb_ask( struct im_connection *ic, char *msg, void *data,
370               query_callback doit, query_callback dont )
371{
372        query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, g_free, data );
373}
374
375void imcb_ask_with_free( struct im_connection *ic, char *msg, void *data,
376                         query_callback doit, query_callback dont, query_callback myfree )
377{
378        query_add( (irc_t *) ic->bee->ui_data, ic, msg, doit, dont, myfree, data );
379}
380
381void imcb_add_buddy( struct im_connection *ic, const char *handle, const char *group )
382{
383        bee_user_t *bu;
384        bee_t *bee = ic->bee;
385        bee_group_t *oldg;
386       
387        if( !( bu = bee_user_by_handle( bee, ic, handle ) ) )
388                bu = bee_user_new( bee, ic, handle, 0 );
389       
390        oldg = bu->group;
391        bu->group = bee_group_by_name( bee, group, TRUE );
392       
393        if( bee->ui->user_group && bu->group != oldg )
394                bee->ui->user_group( bee, bu );
395}
396
397void imcb_rename_buddy( struct im_connection *ic, const char *handle, const char *fullname )
398{
399        bee_t *bee = ic->bee;
400        bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
401       
402        if( !bu || !fullname ) return;
403       
404        if( !bu->fullname || strcmp( bu->fullname, fullname ) != 0 )
405        {
406                g_free( bu->fullname );
407                bu->fullname = g_strdup( fullname );
408               
409                if( bee->ui->user_fullname )
410                        bee->ui->user_fullname( bee, bu );
411        }
412}
413
414void imcb_remove_buddy( struct im_connection *ic, const char *handle, char *group )
415{
416        bee_user_free( ic->bee, bee_user_by_handle( ic->bee, ic, handle ) );
417}
418
419/* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM
420   modules to suggest a nickname for a handle. */
421void imcb_buddy_nick_hint( struct im_connection *ic, const char *handle, const char *nick )
422{
423        bee_t *bee = ic->bee;
424        bee_user_t *bu = bee_user_by_handle( bee, ic, handle );
425       
426        if( !bu || !nick ) return;
427       
428        g_free( bu->nick );
429        bu->nick = g_strdup( nick );
430       
431        if( bee->ui->user_nick_hint )
432                bee->ui->user_nick_hint( bee, bu, nick );
433}
434
435
436struct imcb_ask_cb_data
437{
438        struct im_connection *ic;
439        char *handle;
440};
441
442static void imcb_ask_auth_cb_no( void *data )
443{
444        struct imcb_ask_cb_data *cbd = data;
445       
446        cbd->ic->acc->prpl->auth_deny( cbd->ic, cbd->handle );
447       
448        g_free( cbd->handle );
449        g_free( cbd );
450}
451
452static void imcb_ask_auth_cb_yes( void *data )
453{
454        struct imcb_ask_cb_data *cbd = data;
455       
456        cbd->ic->acc->prpl->auth_allow( cbd->ic, cbd->handle );
457       
458        g_free( cbd->handle );
459        g_free( cbd );
460}
461
462void imcb_ask_auth( struct im_connection *ic, const char *handle, const char *realname )
463{
464        struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 );
465        char *s, *realname_ = NULL;
466       
467        if( realname != NULL )
468                realname_ = g_strdup_printf( " (%s)", realname );
469       
470        s = g_strdup_printf( "The user %s%s wants to add you to his/her buddy list.",
471                             handle, realname_ ? realname_ : "" );
472       
473        g_free( realname_ );
474       
475        data->ic = ic;
476        data->handle = g_strdup( handle );
477        query_add( (irc_t *) ic->bee->ui_data, ic, s,
478                   imcb_ask_auth_cb_yes, imcb_ask_auth_cb_no, g_free, data );
479}
480
481
482static void imcb_ask_add_cb_no( void *data )
483{
484        g_free( ((struct imcb_ask_cb_data*)data)->handle );
485        g_free( data );
486}
487
488static void imcb_ask_add_cb_yes( void *data )
489{
490        struct imcb_ask_cb_data *cbd = data;
491       
492        cbd->ic->acc->prpl->add_buddy( cbd->ic, cbd->handle, NULL );
493       
494        imcb_ask_add_cb_no( data );
495}
496
497void imcb_ask_add( struct im_connection *ic, const char *handle, const char *realname )
498{
499        struct imcb_ask_cb_data *data = g_new0( struct imcb_ask_cb_data, 1 );
500        char *s;
501       
502        /* TODO: Make a setting for this! */
503        if( bee_user_by_handle( ic->bee, ic, handle ) != NULL )
504                return;
505       
506        s = g_strdup_printf( "The user %s is not in your buddy list yet. Do you want to add him/her now?", handle );
507       
508        data->ic = ic;
509        data->handle = g_strdup( handle );
510        query_add( (irc_t *) ic->bee->ui_data, ic, s,
511                   imcb_ask_add_cb_yes, imcb_ask_add_cb_no, g_free, data );
512}
513
514struct bee_user *imcb_buddy_by_handle( struct im_connection *ic, const char *handle )
515{
516        return bee_user_by_handle( ic->bee, ic, handle );
517}
518
519/* The plan is to not allow straight calls to prpl functions anymore, but do
520   them all from some wrappers. We'll start to define some down here: */
521
522int imc_chat_msg( struct groupchat *c, char *msg, int flags )
523{
524        char *buf = NULL;
525       
526        if( ( c->ic->flags & OPT_DOES_HTML ) && ( g_strncasecmp( msg, "<html>", 6 ) != 0 ) )
527        {
528                buf = escape_html( msg );
529                msg = buf;
530        }
531       
532        c->ic->acc->prpl->chat_msg( c, msg, flags );
533        g_free( buf );
534       
535        return 1;
536}
537
538static char *imc_away_state_find( GList *gcm, char *away, char **message );
539
540int imc_away_send_update( struct im_connection *ic )
541{
542        char *away, *msg = NULL;
543       
544        if( ic->acc->prpl->away_states == NULL ||
545            ic->acc->prpl->set_away == NULL )
546                return 0;
547       
548        away = set_getstr( &ic->acc->set, "away" ) ?
549             : set_getstr( &ic->bee->set, "away" );
550        if( away && *away )
551        {
552                GList *m = ic->acc->prpl->away_states( ic );
553                msg = ic->acc->flags & ACC_FLAG_AWAY_MESSAGE ? away : NULL;
554                away = imc_away_state_find( m, away, &msg ) ? : m->data;
555        }
556        else if( ic->acc->flags & ACC_FLAG_STATUS_MESSAGE )
557        {
558                away = NULL;
559                msg = set_getstr( &ic->acc->set, "status" ) ?
560                    : set_getstr( &ic->bee->set, "status" );
561        }
562       
563        ic->acc->prpl->set_away( ic, away, msg );
564       
565        return 1;
566}
567
568static char *imc_away_alias_list[8][5] =
569{
570        { "Away from computer", "Away", "Extended away", NULL },
571        { "NA", "N/A", "Not available", NULL },
572        { "Busy", "Do not disturb", "DND", "Occupied", NULL },
573        { "Be right back", "BRB", NULL },
574        { "On the phone", "Phone", "On phone", NULL },
575        { "Out to lunch", "Lunch", "Food", NULL },
576        { "Invisible", "Hidden" },
577        { NULL }
578};
579
580static char *imc_away_state_find( GList *gcm, char *away, char **message )
581{
582        GList *m;
583        int i, j;
584       
585        for( m = gcm; m; m = m->next )
586                if( g_strncasecmp( m->data, away, strlen( m->data ) ) == 0 )
587                {
588                        /* At least the Yahoo! module works better if message
589                           contains no data unless it adds something to what
590                           we have in state already. */
591                        if( strlen( m->data ) == strlen( away ) )
592                                *message = NULL;
593                       
594                        return m->data;
595                }
596       
597        for( i = 0; *imc_away_alias_list[i]; i ++ )
598        {
599                int keep_message;
600               
601                for( j = 0; imc_away_alias_list[i][j]; j ++ )
602                        if( g_strncasecmp( away, imc_away_alias_list[i][j], strlen( imc_away_alias_list[i][j] ) ) == 0 )
603                        {
604                                keep_message = strlen( away ) != strlen( imc_away_alias_list[i][j] );
605                                break;
606                        }
607               
608                if( !imc_away_alias_list[i][j] )        /* If we reach the end, this row */
609                        continue;                       /* is not what we want. Next!    */
610               
611                /* Now find an entry in this row which exists in gcm */
612                for( j = 0; imc_away_alias_list[i][j]; j ++ )
613                {
614                        for( m = gcm; m; m = m->next )
615                                if( g_strcasecmp( imc_away_alias_list[i][j], m->data ) == 0 )
616                                {
617                                        if( !keep_message )
618                                                *message = NULL;
619                                       
620                                        return imc_away_alias_list[i][j];
621                                }
622                }
623               
624                /* No need to look further, apparently this state doesn't
625                   have any good alias for this protocol. */
626                break;
627        }
628       
629        return NULL;
630}
631
632void imc_add_allow( struct im_connection *ic, char *handle )
633{
634        if( g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL )
635        {
636                ic->permit = g_slist_prepend( ic->permit, g_strdup( handle ) );
637        }
638       
639        ic->acc->prpl->add_permit( ic, handle );
640}
641
642void imc_rem_allow( struct im_connection *ic, char *handle )
643{
644        GSList *l;
645       
646        if( ( l = g_slist_find_custom( ic->permit, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) )
647        {
648                g_free( l->data );
649                ic->permit = g_slist_delete_link( ic->permit, l );
650        }
651       
652        ic->acc->prpl->rem_permit( ic, handle );
653}
654
655void imc_add_block( struct im_connection *ic, char *handle )
656{
657        if( g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) == NULL )
658        {
659                ic->deny = g_slist_prepend( ic->deny, g_strdup( handle ) );
660        }
661       
662        ic->acc->prpl->add_deny( ic, handle );
663}
664
665void imc_rem_block( struct im_connection *ic, char *handle )
666{
667        GSList *l;
668       
669        if( ( l = g_slist_find_custom( ic->deny, handle, (GCompareFunc) ic->acc->prpl->handle_cmp ) ) )
670        {
671                g_free( l->data );
672                ic->deny = g_slist_delete_link( ic->deny, l );
673        }
674       
675        ic->acc->prpl->rem_deny( ic, handle );
676}
677
678void imcb_clean_handle( struct im_connection *ic, char *handle )
679{
680        /* Accepts a handle and does whatever is necessary to make it
681           BitlBee-friendly. Currently this means removing everything
682           outside 33-127 (ASCII printable excl spaces), @ (only one
683           is allowed) and ! and : */
684        char out[strlen(handle)+1];
685        int s, d;
686       
687        s = d = 0;
688        while( handle[s] )
689        {
690                if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' &&
691                    ( handle[s] & 0x80 ) == 0 )
692                {
693                        if( handle[s] == '@' )
694                        {
695                                /* See if we got an @ already? */
696                                out[d] = 0;
697                                if( strchr( out, '@' ) )
698                                        continue;
699                        }
700                       
701                        out[d++] = handle[s];
702                }
703                s ++;
704        }
705        out[d] = handle[s];
706       
707        strcpy( handle, out );
708}
Note: See TracBrowser for help on using the repository browser.