/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
* Copyright 2002-2004 Wilmer van der Gaast and others *
\********************************************************************/
/* Support for multiple storage backends */
/* Copyright (C) 2005 Jelmer Vernooij <jelmer@samba.org> */
/*
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., 59 Temple Place,
Suite 330, Boston, MA 02111-1307 USA
*/
#define BITLBEE_CORE
#include "bitlbee.h"
#include "crypting.h"
extern storage_t storage_text;
extern storage_t storage_xml;
static GList *storage_backends = NULL;
void register_storage_backend(storage_t *backend)
{
storage_backends = g_list_append(storage_backends, backend);
}
static storage_t *storage_init_single(const char *name)
{
GList *gl;
storage_t *st pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ /********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
* Copyright 2002-2010 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., 59 Temple Place,
Suite 330, Boston, MA 02111-1307 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 |= ACC_SET_NOSAVE;
s = set_add( &a->set, "auto_reconnect", "true", set_eval_bool, a );
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 |= ACC_SET_NOSAVE; /* Just for bw compatibility! */
s = set_add( &a->set, "password", NULL, set_eval_account, a );
s->flags |= ACC_SET_NOSAVE | SET_NULL_OK | SET_PASSWORD;
s = set_add( &a->set, "tag", NULL, set_eval_account, a );
s->flags |= ACC_SET_NOSAVE;
s = set_add( &a->set, "username", NULL, set_eval_account, a );
s->flags |= ACC_SET_NOSAVE | ACC_SET_OFFLINE_ONLY;
set_setstr( &a->set, "username", user );
/* Hardcode some more clever tag guesses. */
strcpy( tag, prpl->name );
if( strcmp( prpl->name, "oscar" ) == 0 )
{
if( 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" );
else if( strstr( a->user, "@chat.facebook.com" ) )
strcpy( tag, "fb" );
}
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 )
{
if( value )
{
g_free( acc->pass );
acc->pass = g_strdup( value );
return NULL; /* password shouldn't be visible in plaintext! */
}
else
{
/* NULL can (should) be stored in the set_t
variable, but is otherwise not correct. */
return SET_INVALID;
}
}
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 ) )
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;
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 && 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 && 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 && 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;
}