source: protocols/account.c @ 87872c7

Last change on this file since 87872c7 was 5a8afc3, checked in by dequis <dx@…>, at 2016-11-21T06:58:47Z

Manual merge with wilmer's approach to handling missing protocols

Turns out he already implemented pretty much the same thing in the
parson branch... last year.

The differences between the two approaches are subtle (there aren't too
many ways to do this, some lines are the exact same thing) but I decided
I like his version better, so this mostly reverts a handful of my
changes while keeping others. The main advantage of his approach is that
no fake protocols are registered, no actual prpl functions are called,
and the missing prpl is a singleton constant.

New things compared to the implementation in the other branch:

  • The explain_unknown_protocol() function.
  • Fixed named chatrooms throwing a warning and losing the "account" setting when saving. See changes in irc_im.c
  • Fixed the "server" setting dropping when saving. See account.c

Differences with my previous implementation:

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