/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2013 Wilmer van der Gaast and others * \********************************************************************/ /* Account management functions */ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License with the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "account.h" static char *set_eval_nick_source(set_t *set, char *value); account_t *account_add(bee_t *bee, struct prpl *prpl, char *user, char *pass) { account_t *a; set_t *s; char tag[strlen(prpl->name) + 10]; if (bee->accounts) { for (a = bee->accounts; a->next; a = a->next) { ; } a = a->next = g_new0(account_t, 1); } else { bee->accounts = a = g_new0(account_t, 1); } a->prpl = prpl; a->user = g_strdup(user); a->pass = g_strdup(pass); a->auto_connect = 1; a->bee = bee; s = set_add(&a->set, "auto_connect", "true", set_eval_account, a); s->flags |= SET_NOSAVE; s = set_add(&a->set, "auto_reconnect", "true", set_eval_bool, a); s = set_add(&a->set, "handle_unknown", NULL, NULL, a); s->flags |= SET_NULL_OK; s = set_add(&a->set, "nick_format", NULL, NULL, a); s->flags |= SET_NULL_OK; s = set_add(&a->set, "nick_source", "handle", set_eval_nick_source, a); s->flags |= SET_NOSAVE; /* Just for bw compatibility! */ s = set_add(&a->set, "password", NULL, set_eval_account, a); s->flags |= SET_NOSAVE | SET_NULL_OK | SET_PASSWORD | ACC_SET_LOCKABLE; s = set_add(&a->set, "tag", NULL, set_eval_account, a); s->flags |= SET_NOSAVE; s = set_add(&a->set, "username", NULL, set_eval_account, a); s->flags |= SET_NOSAVE | ACC_SET_OFFLINE_ONLY | ACC_SET_LOCKABLE; set_setstr(&a->set, "username", user); if (prpl == &protocol_missing) { s = set_add(&a->set, "server", NULL, set_eval_account, a); s->flags |= SET_NOSAVE | SET_HIDDEN | ACC_SET_OFFLINE_ONLY | ACC_SET_ONLINE_ONLY; } /* Hardcode some more clever tag guesses. */ strcpy(tag, prpl->name); if (strcmp(prpl->name, "oscar") == 0) { if (g_ascii_isdigit(a->user[0])) { strcpy(tag, "icq"); } else { strcpy(tag, "aim"); } } else if (strcmp(prpl->name, "jabber") == 0) { if (strstr(a->user, "@gmail.com") || strstr(a->user, "@googlemail.com")) { strcpy(tag, "gtalk"); } } if (account_by_tag(bee, tag)) { char *numpos = tag + strlen(tag); int i; for (i = 2; i < 10000; i++) { sprintf(numpos, "%d", i); if (!account_by_tag(bee, tag)) { break; } } } set_setstr(&a->set, "tag", tag); a->nicks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); /* This function adds some more settings (and might want to do more things that have to be done now, although I can't think of anything. */ if (prpl->init) { prpl->init(a); } s = set_add(&a->set, "away", NULL, set_eval_account, a); s->flags |= SET_NULL_OK; if (a->flags & ACC_FLAG_STATUS_MESSAGE) { s = set_add(&a->set, "status", NULL, set_eval_account, a); s->flags |= SET_NULL_OK; } return a; } char *set_eval_account(set_t *set, char *value) { account_t *acc = set->data; /* Double-check: We refuse to edit on-line accounts. */ if (set->flags & ACC_SET_OFFLINE_ONLY && acc->ic) { return SET_INVALID; } if (strcmp(set->key, "server") == 0) { g_free(acc->server); if (value && *value) { acc->server = g_strdup(value); return value; } else { acc->server = g_strdup(set->def); return g_strdup(set->def); } } else if (strcmp(set->key, "username") == 0) { g_free(acc->user); acc->user = g_strdup(value); return value; } else if (strcmp(set->key, "password") == 0) { /* set -del allows /oper to be used to change the password or, iff oauth is enabled, reset the oauth credential magic. */ if (!value) { if (set_getbool(&(acc->set), "oauth")) { value = ""; } else { value = PASSWORD_PENDING; ((irc_t *) acc->bee->ui_data)->status |= OPER_HACK_ACCOUNT_PASSWORD; irc_rootmsg((irc_t *) acc->bee->ui_data, "You may now use /OPER to set the password"); } } g_free(acc->pass); acc->pass = g_strdup(value); return NULL; /* password shouldn't be visible in plaintext! */ } else if (strcmp(set->key, "tag") == 0) { account_t *oa; /* Enforce uniqueness. */ if ((oa = account_by_tag(acc->bee, value)) && oa != acc) { return SET_INVALID; } g_free(acc->tag); acc->tag = g_strdup(value); return value; } else if (strcmp(set->key, "auto_connect") == 0) { if (!is_bool(value)) { return SET_INVALID; } acc->auto_connect = bool2int(value); return value; } else if (strcmp(set->key, "away") == 0 || strcmp(set->key, "status") == 0) { if (acc->ic && acc->ic->flags & OPT_LOGGED_IN) { /* If we're currently on-line, set the var now already (bit of a hack) and send an update. */ g_free(set->value); set->value = g_strdup(value); imc_away_send_update(acc->ic); } return value; } return SET_INVALID; } /* For bw compatibility, have this write-only setting. */ static char *set_eval_nick_source(set_t *set, char *value) { account_t *a = set->data; if (strcmp(value, "full_name") == 0) { set_setstr(&a->set, "nick_format", "%full_name"); } else if (strcmp(value, "first_name") == 0) { set_setstr(&a->set, "nick_format", "%first_name"); } else { set_setstr(&a->set, "nick_format", "%-@nick"); } return value; } account_t *account_get(bee_t *bee, const char *id) { account_t *a, *ret = NULL; char *handle, *s; int nr; /* Tags get priority above anything else. */ if ((a = account_by_tag(bee, id))) { return a; } /* This checks if the id string ends with (...) */ if ((handle = strchr(id, '(')) && (s = strchr(handle, ')')) && s[1] == 0) { struct prpl *proto; *s = *handle = 0; handle++; if ((proto = find_protocol(id))) { for (a = bee->accounts; a; a = a->next) { if (a->prpl == proto && a->prpl->handle_cmp(handle, a->user) == 0) { ret = a; } } } /* Restore the string. */ handle--; *handle = '('; *s = ')'; if (ret) { return ret; } } if (sscanf(id, "%d", &nr) == 1 && nr < 1000) { for (a = bee->accounts; a; a = a->next) { if ((nr--) == 0) { return(a); } } return(NULL); } for (a = bee->accounts; a; a = a->next) { if (g_strcasecmp(id, a->prpl->name) == 0) { if (!ret) { ret = a; } else { return(NULL); /* We don't want to match more than one... */ } } else if (strstr(a->user, id)) { if (!ret) { ret = a; } else { return(NULL); } } } return(ret); } account_t *account_by_tag(bee_t *bee, const char *tag) { account_t *a; for (a = bee->accounts; a; a = a->next) { if (a->tag && g_strcasecmp(tag, a->tag) == 0) { return a; } } return NULL; } void account_del(bee_t *bee, account_t *acc) { account_t *a, *l = NULL; if (acc->ic) { /* Caller should have checked, accounts still in use can't be deleted. */ return; } for (a = bee->accounts; a; a = (l = a)->next) { if (a == acc) { if (l) { l->next = a->next; } else { bee->accounts = a->next; } /** FIXME for( c = bee->chatrooms; c; c = nc ) { nc = c->next; if( acc == c->acc ) chat_del( bee, c ); } */ while (a->set) { set_del(&a->set, a->set->key); } g_hash_table_destroy(a->nicks); g_free(a->tag); g_free(a->user); g_free(a->pass); g_free(a->server); if (a->reconnect) { /* This prevents any reconnect still queued to happen */ cancel_auto_reconnect(a); } g_free(a); break; } } } static gboolean account_on_timeout(gpointer d, gint fd, b_input_condition cond); void account_on(bee_t *bee, account_t *a) { if (a->ic) { /* Trying to enable an already-enabled account */ return; } cancel_auto_reconnect(a); a->reconnect = 0; a->prpl->login(a); if (a->ic && !(a->ic->flags & (OPT_SLOW_LOGIN | OPT_LOGGED_IN))) { a->ic->keepalive = b_timeout_add(120000, account_on_timeout, a->ic); } } void account_off(bee_t *bee, account_t *a) { imc_logout(a->ic, FALSE); a->ic = NULL; if (a->reconnect) { /* Shouldn't happen */ cancel_auto_reconnect(a); } } static gboolean account_on_timeout(gpointer d, gint fd, b_input_condition cond) { struct im_connection *ic = d; if (!(ic->flags & (OPT_SLOW_LOGIN | OPT_LOGGED_IN))) { imcb_error(ic, "Connection timeout"); imc_logout(ic, TRUE); } return FALSE; } struct account_reconnect_delay { int start; char op; int step; int max; }; int account_reconnect_delay_parse(char *value, struct account_reconnect_delay *p) { memset(p, 0, sizeof(*p)); /* A whole day seems like a sane "maximum maximum". */ p->max = 86400; /* Format: /[0-9]+([*+][0-9]+(<[0-9+])?)?/ */ while (*value && g_ascii_isdigit(*value)) { p->start = p->start * 10 + *value++ - '0'; } /* Sure, call me evil for implementing my own fscanf here, but it's dead simple and I immediately know where to continue parsing. */ if (*value == 0) { /* If the string ends now, the delay is constant. */ return 1; } else if (*value != '+' && *value != '*') { /* Otherwise allow either a + or a * */ return 0; } p->op = *value++; /* + or * the delay by this number every time. */ while (*value && g_ascii_isdigit(*value)) { p->step = p->step * 10 + *value++ - '0'; } if (*value == 0) { /* Use the default maximum (one day). */ return 1; } else if (*value != '<') { return 0; } p->max = 0; value++; while (*value && g_ascii_isdigit(*value)) { p->max = p->max * 10 + *value++ - '0'; } return p->max > 0; } char *set_eval_account_reconnect_delay(set_t *set, char *value) { struct account_reconnect_delay p; return account_reconnect_delay_parse(value, &p) ? value : SET_INVALID; } int account_reconnect_delay(account_t *a) { char *setting = set_getstr(&a->bee->set, "auto_reconnect_delay"); struct account_reconnect_delay p; if (account_reconnect_delay_parse(setting, &p)) { if (a->auto_reconnect_delay == 0) { a->auto_reconnect_delay = p.start; } else if (p.op == '+') { a->auto_reconnect_delay += p.step; } else if (p.op == '*') { a->auto_reconnect_delay *= p.step; } if (a->auto_reconnect_delay > p.max) { a->auto_reconnect_delay = p.max; } } else { a->auto_reconnect_delay = 0; } return a->auto_reconnect_delay; }