source: protocols/account.c @ b1b9453

Last change on this file since b1b9453 was 7801298, checked in by dequis <dx@…>, at 2016-12-27T17:24:50Z

Per-account handle_unknown

Credit for the idea goes to russian XMPP spammers. Thanks!

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