source: protocols/account.c @ 8e6ecfe

Last change on this file since 8e6ecfe was 3ac6d9f, checked in by Dennis Kaarsemaker <dennis@…>, at 2016-03-23T06:44:13Z

Support for locked-down accounts

In certain situations, e.g. when working with pregenerated
configurations, it is useful to be able lock down accounts so they
cannot be deleted and authentication information (user, password,
server) cannot be changed.

We mark such sensitive settings with ACC_SET_LOCKABLE and will refuse to
change them if the account is locked by setting the ACC_FLAG_LOCKED
flag.

This flag is stored in the xml files as account attribute locked="true".

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