source: protocols/account.c @ 1fa5109

Last change on this file since 1fa5109 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: 11.1 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/* Account management functions                                         */
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#define BITLBEE_CORE
27#include "bitlbee.h"
28#include "account.h"
29
30static const char* account_protocols_local[] = {
31        "gg", "whatsapp", NULL
32};
33
34static char *set_eval_nick_source( set_t *set, char *value );
35
36account_t *account_add( bee_t *bee, struct prpl *prpl, char *user, char *pass )
37{
38        account_t *a;
39        set_t *s;
40        char tag[strlen(prpl->name)+10];
41       
42        if( bee->accounts )
43        {
44                for( a = bee->accounts; a->next; a = a->next );
45                a = a->next = g_new0( account_t, 1 );
46        }
47        else
48        {
49                bee->accounts = a = g_new0 ( account_t, 1 );
50        }
51       
52        a->prpl = prpl;
53        a->user = g_strdup( user );
54        a->pass = g_strdup( pass );
55        a->auto_connect = 1;
56        a->bee = bee;
57       
58        s = set_add( &a->set, "auto_connect", "true", set_eval_account, a );
59        s->flags |= SET_NOSAVE;
60       
61        s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a );
62       
63        s = set_add( &a->set, "nick_format", NULL, NULL, a );
64        s->flags |= SET_NULL_OK;
65       
66        s = set_add( &a->set, "nick_source", "handle", set_eval_nick_source, a );
67        s->flags |= SET_NOSAVE; /* Just for bw compatibility! */
68       
69        s = set_add( &a->set, "password", NULL, set_eval_account, a );
70        s->flags |= SET_NOSAVE | SET_NULL_OK | SET_PASSWORD;
71       
72        s = set_add( &a->set, "tag", NULL, set_eval_account, a );
73        s->flags |= SET_NOSAVE;
74       
75        s = set_add( &a->set, "username", NULL, set_eval_account, a );
76        s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
77        set_setstr( &a->set, "username", user );
78       
79        /* Hardcode some more clever tag guesses. */
80        strcpy( tag, prpl->name );
81        if( strcmp( prpl->name, "oscar" ) == 0 )
82        {
83                if( g_ascii_isdigit( a->user[0] ) )
84                        strcpy( tag, "icq" );
85                else
86                        strcpy( tag, "aim" );
87        }
88        else if( strcmp( prpl->name, "jabber" ) == 0 )
89        {
90                if( strstr( a->user, "@gmail.com" ) ||
91                    strstr( a->user, "@googlemail.com" ) )
92                        strcpy( tag, "gtalk" );
93                else if( strstr( a->user, "@chat.facebook.com" ) )
94                        strcpy( tag, "fb" );
95        }
96       
97        if( account_by_tag( bee, tag ) )
98        {
99                char *numpos = tag + strlen( tag );
100                int i;
101
102                for( i = 2; i < 10000; i ++ )
103                {
104                        sprintf( numpos, "%d", i );
105                        if( !account_by_tag( bee, tag ) )
106                                break;
107                }
108        }
109        set_setstr( &a->set, "tag", tag );
110       
111        a->nicks = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free );
112       
113        /* This function adds some more settings (and might want to do more
114           things that have to be done now, although I can't think of anything. */
115        if( prpl->init )
116                prpl->init( a );
117       
118        s = set_add( &a->set, "away", NULL, set_eval_account, a );
119        s->flags |= SET_NULL_OK;
120       
121        if( a->flags & ACC_FLAG_STATUS_MESSAGE )
122        {
123                s = set_add( &a->set, "status", NULL, set_eval_account, a );
124                s->flags |= SET_NULL_OK;
125        }
126       
127        return a;
128}
129
130char *set_eval_account( set_t *set, char *value )
131{
132        account_t *acc = set->data;
133       
134        /* Double-check: We refuse to edit on-line accounts. */
135        if( set->flags & ACC_SET_OFFLINE_ONLY && acc->ic )
136                return SET_INVALID;
137       
138        if( strcmp( set->key, "server" ) == 0 )
139        {
140                g_free( acc->server );
141                if( value && *value )
142                {
143                        acc->server = g_strdup( value );
144                        return value;
145                }
146                else
147                {
148                        acc->server = g_strdup( set->def );
149                        return g_strdup( set->def );
150                }
151        }
152        else if( strcmp( set->key, "username" ) == 0 )
153        {
154                g_free( acc->user );
155                acc->user = g_strdup( value );
156                return value;
157        }
158        else if( strcmp( set->key, "password" ) == 0 )
159        {
160                /* set -del allows /oper to be used to change the password or,
161                   iff oauth is enabled, reset the oauth credential magic.
162                */
163                if( !value ) {
164                        if ( set_getbool( &(acc->set), "oauth" ) ) {
165                                value = "";
166                        } else {
167                                value = PASSWORD_PENDING;
168                                ((irc_t *)acc->bee->ui_data)->status |= OPER_HACK_ACCOUNT_PASSWORD;
169                                irc_rootmsg((irc_t *)acc->bee->ui_data, "You may now use /OPER to set the password");
170                        }
171                }
172               
173                g_free( acc->pass );
174                acc->pass = g_strdup( value );
175                return NULL;    /* password shouldn't be visible in plaintext! */
176        }
177        else if( strcmp( set->key, "tag" ) == 0 )
178        {
179                account_t *oa;
180               
181                /* Enforce uniqueness. */
182                if( ( oa = account_by_tag( acc->bee, value ) ) && oa != acc )
183                        return SET_INVALID;
184               
185                g_free( acc->tag );
186                acc->tag = g_strdup( value );
187                return value;
188        }
189        else if( strcmp( set->key, "auto_connect" ) == 0 )
190        {
191                if( !is_bool( value ) )
192                        return SET_INVALID;
193               
194                acc->auto_connect = bool2int( value );
195                return value;
196        }
197        else if( strcmp( set->key, "away" ) == 0 ||
198                 strcmp( set->key, "status" ) == 0 )
199        {
200                if( acc->ic && acc->ic->flags & OPT_LOGGED_IN )
201                {
202                        /* If we're currently on-line, set the var now already
203                           (bit of a hack) and send an update. */
204                        g_free( set->value );
205                        set->value = g_strdup( value );
206                       
207                        imc_away_send_update( acc->ic );
208                }
209               
210                return value;
211        }
212       
213        return SET_INVALID;
214}
215
216/* For bw compatibility, have this write-only setting. */
217static char *set_eval_nick_source( set_t *set, char *value )
218{
219        account_t *a = set->data;
220       
221        if( strcmp( value, "full_name" ) == 0 )
222                set_setstr( &a->set, "nick_format", "%full_name" );
223        else if( strcmp( value, "first_name" ) == 0 )
224                set_setstr( &a->set, "nick_format", "%first_name" );
225        else
226                set_setstr( &a->set, "nick_format", "%-@nick" );
227       
228        return value;
229}
230
231account_t *account_get( bee_t *bee, const char *id )
232{
233        account_t *a, *ret = NULL;
234        char *handle, *s;
235        int nr;
236       
237        /* Tags get priority above anything else. */
238        if( ( a = account_by_tag( bee, id ) ) )
239                return a;
240       
241        /* This checks if the id string ends with (...) */
242        if( ( handle = strchr( id, '(' ) ) && ( s = strchr( handle, ')' ) ) && s[1] == 0 )
243        {
244                struct prpl *proto;
245               
246                *s = *handle = 0;
247                handle ++;
248               
249                if( ( proto = find_protocol( id ) ) )
250                {
251                        for( a = bee->accounts; a; a = a->next )
252                                if( a->prpl == proto &&
253                                    a->prpl->handle_cmp( handle, a->user ) == 0 )
254                                        ret = a;
255                }
256               
257                /* Restore the string. */
258                handle --;
259                *handle = '(';
260                *s = ')';
261               
262                if( ret )
263                        return ret;
264        }
265       
266        if( sscanf( id, "%d", &nr ) == 1 && nr < 1000 )
267        {
268                for( a = bee->accounts; a; a = a->next )
269                        if( ( nr-- ) == 0 )
270                                return( a );
271               
272                return( NULL );
273        }
274       
275        for( a = bee->accounts; a; a = a->next )
276        {
277                if( g_strcasecmp( id, a->prpl->name ) == 0 )
278                {
279                        if( !ret )
280                                ret = a;
281                        else
282                                return( NULL ); /* We don't want to match more than one... */
283                }
284                else if( strstr( a->user, id ) )
285                {
286                        if( !ret )
287                                ret = a;
288                        else
289                                return( NULL );
290                }
291        }
292       
293        return( ret );
294}
295
296account_t *account_by_tag( bee_t *bee, const char *tag )
297{
298        account_t *a;
299       
300        for( a = bee->accounts; a; a = a->next )
301                if( a->tag && g_strcasecmp( tag, a->tag ) == 0 )
302                        return a;
303       
304        return NULL;
305}
306
307void account_del( bee_t *bee, account_t *acc )
308{
309        account_t *a, *l = NULL;
310       
311        if( acc->ic )
312                /* Caller should have checked, accounts still in use can't be deleted. */
313                return;
314       
315        for( a = bee->accounts; a; a = (l=a)->next )
316                if( a == acc )
317                {
318                        if( l )
319                                l->next = a->next;
320                        else
321                                bee->accounts = a->next;
322                       
323                        /** FIXME
324                        for( c = bee->chatrooms; c; c = nc )
325                        {
326                                nc = c->next;
327                                if( acc == c->acc )
328                                        chat_del( bee, c );
329                        }
330                        */
331                       
332                        while( a->set )
333                                set_del( &a->set, a->set->key );
334                       
335                        g_hash_table_destroy( a->nicks );
336                       
337                        g_free( a->tag );
338                        g_free( a->user );
339                        g_free( a->pass );
340                        g_free( a->server );
341                        if( a->reconnect )      /* This prevents any reconnect still queued to happen */
342                                cancel_auto_reconnect( a );
343                        g_free( a );
344                       
345                        break;
346                }
347}
348
349static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond );
350
351void account_on( bee_t *bee, account_t *a )
352{
353        if( a->ic )
354        {
355                /* Trying to enable an already-enabled account */
356                return;
357        }
358       
359        cancel_auto_reconnect( a );
360       
361        a->reconnect = 0;
362        a->prpl->login( a );
363       
364        if( a->ic && !( a->ic->flags & ( OPT_SLOW_LOGIN | OPT_LOGGED_IN ) ) )
365                a->ic->keepalive = b_timeout_add( 120000, account_on_timeout, a->ic );
366}
367
368void account_off( bee_t *bee, account_t *a )
369{
370        imc_logout( a->ic, FALSE );
371        a->ic = NULL;
372        if( a->reconnect )
373        {
374                /* Shouldn't happen */
375                cancel_auto_reconnect( a );
376        }
377}
378
379static gboolean account_on_timeout( gpointer d, gint fd, b_input_condition cond )
380{
381        struct im_connection *ic = d;
382       
383        if( !( ic->flags & ( OPT_SLOW_LOGIN | OPT_LOGGED_IN ) ) )
384        {
385                imcb_error( ic, "Connection timeout" );
386                imc_logout( ic, TRUE );
387        }
388       
389        return FALSE;
390}
391
392struct account_reconnect_delay
393{
394        int start;
395        char op;
396        int step;
397        int max;
398};
399
400int account_reconnect_delay_parse( char *value, struct account_reconnect_delay *p )
401{
402        memset( p, 0, sizeof( *p ) );
403        /* A whole day seems like a sane "maximum maximum". */
404        p->max = 86400;
405       
406        /* Format: /[0-9]+([*+][0-9]+(<[0-9+])?)?/ */
407        while( *value && g_ascii_isdigit( *value ) )
408                p->start = p->start * 10 + *value++ - '0';
409       
410        /* Sure, call me evil for implementing my own fscanf here, but it's
411           dead simple and I immediately know where to continue parsing. */
412       
413        if( *value == 0 )
414                /* If the string ends now, the delay is constant. */
415                return 1;
416        else if( *value != '+' && *value != '*' )
417                /* Otherwise allow either a + or a * */
418                return 0;
419       
420        p->op = *value++;
421       
422        /* + or * the delay by this number every time. */
423        while( *value && g_ascii_isdigit( *value ) )
424                p->step = p->step * 10 + *value++ - '0';
425       
426        if( *value == 0 )
427                /* Use the default maximum (one day). */
428                return 1;
429        else if( *value != '<' )
430                return 0;
431       
432        p->max = 0;
433        value ++;
434        while( *value && g_ascii_isdigit( *value ) )
435                p->max = p->max * 10 + *value++ - '0';
436       
437        return p->max > 0;
438}
439
440char *set_eval_account_reconnect_delay( set_t *set, char *value )
441{
442        struct account_reconnect_delay p;
443       
444        return account_reconnect_delay_parse( value, &p ) ? value : SET_INVALID;
445}
446
447int account_reconnect_delay( account_t *a )
448{
449        char *setting = set_getstr( &a->bee->set, "auto_reconnect_delay" );
450        struct account_reconnect_delay p;
451       
452        if( account_reconnect_delay_parse( setting, &p ) )
453        {
454                if( a->auto_reconnect_delay == 0 )
455                        a->auto_reconnect_delay = p.start;
456                else if( p.op == '+' )
457                        a->auto_reconnect_delay += p.step;
458                else if( p.op == '*' )
459                        a->auto_reconnect_delay *= p.step;
460               
461                if( a->auto_reconnect_delay > p.max )
462                        a->auto_reconnect_delay = p.max;
463        }
464        else
465        {
466                a->auto_reconnect_delay = 0;
467        }
468       
469        return a->auto_reconnect_delay;
470}
471
472int protocol_account_islocal( const char* protocol )
473{
474        const char** p = account_protocols_local;
475        do {
476                if( strcmp( *p, protocol ) == 0 )
477                        return 1;
478        } while( *( ++p ) );
479        return 0;
480}
Note: See TracBrowser for help on using the repository browser.