static void xml_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error ) { struct xml_parsedata *xd = data; irc_t *irc = xd->irc; if( xd->unknown_tag > 0 ) { xd->unknown_tag ++; } else if( g_strcasecmp( element_name, "user" ) == 0 ) { char *nick = xml_attr( attr_names, attr_values, "nick" ); char *pass = xml_attr( attr_names, attr_values, "password" ); int st; if( !nick || !pass ) { g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Missing attributes for %s element", element_name ); } else if( ( st = md5_verify_password( xd->given_pass, pass ) ) == -1 ) { xd->pass_st = XML_PASS_WRONG; g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Error while decoding password attribute" ); } else if( st == 0 ) { if( xd->pass_st != XML_PASS_CHECK_ONLY ) xd->pass_st = XML_PASS_OK; } else { xd->pass_st = XML_PASS_WRONG; g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Password mismatch" ); } } else if( xd->pass_st < XML_PASS_OK ) { /* Let's not parse anything else if we only have to check the password. */ } else if( g_strcasecmp( element_name, "account" ) == 0 ) { char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag; char *pass_b64 = NULL; unsigned char *pass_cr = NULL; int pass_len; struct prpl *prpl = NULL; handle = xml_attr( attr_names, attr_values, "handle" ); pass_b64 = xml_attr( attr_names, attr_values, "password" ); server = xml_attr( attr_names, attr_values, "server" ); autoconnect = xml_attr( attr_names, attr_values, "autoconnect" ); tag = xml_attr( attr_names, attr_values, "tag" ); protocol = xml_attr( attr_names, attr_values, "protocol" ); if( protocol ) prpl = find_protocol( protocol ); if( !handle || !pass_b64 || !protocol ) g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Missing attributes for %s element", element_name ); else if( !prpl ) g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Unknown protocol: %s", protocol ); else if( ( pass_len = base64_decode( pass_b64, (unsigned char**) &pass_cr ) ) && arc_decode( pass_cr, pass_len, &password, xd->given_pass ) >= 0 ) { xd->current_account = account_add( irc->b, prpl, handle, password ); if( server ) set_setstr( &xd->current_account->set, "server", server ); if( autoconnect ) set_setstr( &xd->current_account->set, "auto_connect", autoconnect ); if( tag ) set_setstr( &xd->current_account->set, "tag", tag ); } else { /* Actually the _decode functions don't even return error codes, but maybe they will later... */ g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Error while decrypting account password" ); } g_free( pass_cr ); g_free( password ); } else if( g_strcasecmp( element_name, "setting" ) == 0 ) { char *setting; if( xd->current_setting ) { g_free( xd->current_setting ); xd->current_setting = NULL; } if( ( setting = xml_attr( attr_names, attr_values, "name" ) ) ) { if( xd->current_channel != NULL ) xd->current_set_head = &xd->current_channel->set; else if( xd->current_account != NULL ) xd->current_set_head = &xd->current_account->set; else xd->current_set_head = &xd->irc->b->set; xd->current_setting = g_strdup( setting ); } else g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Missing attributes for %s element", element_name ); } else if( g_strcasecmp( element_name, "buddy" ) == 0 ) { char *handle, *nick; handle = xml_attr( attr_names, attr_values, "handle" ); nick = xml_attr( attr_names, attr_values, "nick" ); if( xd->current_account && handle && nick ) { nick_set_raw( xd->current_account, handle, nick ); } else { g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Missing attributes for %s element", element_name ); } } else if( g_strcasecmp( element_name, "channel" ) == 0 ) { char *name, *type; name = xml_attr( attr_names, attr_values, "name" ); type = xml_attr( attr_names, attr_values, "type" ); if( !name || !type ) { g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Missing attributes for %s element", element_name ); return; } /* The channel may exist already, for example if it's &bitlbee. Also, it's possible that the user just reconnected and the IRC client already rejoined all channels it was in. They should still get the right settings. */ if( ( xd->current_channel = irc_channel_by_name( irc, name ) ) || ( xd->current_channel = irc_channel_new( irc, name ) ) ) set_setstr(&xd->current_channel->set, "type", type ); } /* Backward compatibility: Keep this around for a while for people switching from BitlBee 1.2.4+. */ else if( g_strcasecmp( element_name, "chat" ) == 0 ) { char *handle, *channel; handle = xml_attr( attr_names, attr_values, "handle" ); channel = xml_attr( attr_names, attr_values, "channel" ); if( xd->current_account && handle && channel ) { irc_channel_t *ic; if( ( ic = irc_channel_new( irc, channel ) ) && set_setstr( &ic->set, "type", "chat" ) && set_setstr( &ic->set, "chat_type", "room" ) && set_setstr( &ic->set, "account", xd->current_account->tag ) && set_setstr( &ic->set, "room", handle ) ) { /* Try to pick up some settings where possible. */ xd->current_channel = ic; } else if( ic ) irc_channel_free( ic ); } else { g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Missing attributes for %s element", element_name ); } } else { xd->unknown_tag ++; irc_rootmsg( irc, "Warning: Unknown XML tag found in configuration file (%s). " "This may happen when downgrading BitlBee versions. " "This tag will be skipped and the information will be lost " "once you save your settings.", element_name ); /* g_set_error( error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, "Unkown element: %s", element_name ); */ } }
static storage_status_t xml_load_real( irc_t *irc, const char *my_nick, const char *password, xml_pass_st action ) { GMarkupParseContext *ctx; struct xml_parsedata *xd; char *fn, buf[512]; GError *gerr = NULL; int fd, st; xd = g_new0( struct xml_parsedata, 1 ); xd->irc = irc; xd->given_nick = g_strdup( my_nick ); xd->given_pass = g_strdup( password ); xd->pass_st = action; nick_lc( xd->given_nick ); fn = g_strdup_printf( "%s%s%s", global.conf->configdir, xd->given_nick, ".xml" ); if( ( fd = open( fn, O_RDONLY ) ) < 0 ) { xml_destroy_xd( xd ); g_free( fn ); return STORAGE_NO_SUCH_USER; } g_free( fn ); ctx = g_markup_parse_context_new( &xml_parser, 0, xd, xml_destroy_xd ); while( ( st = read( fd, buf, sizeof( buf ) ) ) > 0 ) { if( !g_markup_parse_context_parse( ctx, buf, st, &gerr ) || gerr ) { xml_pass_st pass_st = xd->pass_st; g_markup_parse_context_free( ctx ); close( fd ); if( pass_st == XML_PASS_WRONG ) { g_clear_error( &gerr ); return STORAGE_INVALID_PASSWORD; } else { if( gerr && irc ) irc_rootmsg( irc, "Error from XML-parser: %s", gerr->message ); g_clear_error( &gerr ); return STORAGE_OTHER_ERROR; } } } /* Just to be sure... */ g_clear_error( &gerr ); g_markup_parse_context_free( ctx ); close( fd ); if( action == XML_PASS_CHECK_ONLY ) return STORAGE_OK; return STORAGE_OK; }
static storage_status_t xml_save( irc_t *irc, int overwrite ) { char path[512], *path2, *pass_buf = NULL; set_t *set; account_t *acc; int fd; md5_byte_t pass_md5[21]; md5_state_t md5_state; GSList *l; path2 = g_strdup( irc->user->nick ); nick_lc( path2 ); g_snprintf( path, sizeof( path ) - 2, "%s%s%s", global.conf->configdir, path2, ".xml" ); g_free( path2 ); if( !overwrite && g_access( path, F_OK ) == 0 ) return STORAGE_ALREADY_EXISTS; strcat( path, ".XXXXXX" ); if( ( fd = mkstemp( path ) ) < 0 ) { irc_rootmsg( irc, "Error while opening configuration file." ); return STORAGE_OTHER_ERROR; } /* Generate a salted md5sum of the password. Use 5 bytes for the salt (to prevent dictionary lookups of passwords) to end up with a 21- byte password hash, more convenient for base64 encoding. */ random_bytes( pass_md5 + 16, 5 ); md5_init( &md5_state ); md5_append( &md5_state, (md5_byte_t*) irc->password, strlen( irc->password ) ); md5_append( &md5_state, pass_md5 + 16, 5 ); /* Add the salt. */ md5_finish( &md5_state, pass_md5 ); /* Save the hash in base64-encoded form. */ pass_buf = base64_encode( pass_md5, 21 ); if( !xml_printf( fd, 0, "<user nick=\"%s\" password=\"%s\" version=\"%d\">\n", irc->user->nick, pass_buf, XML_FORMAT_VERSION ) ) goto write_error; g_free( pass_buf ); for( set = irc->b->set; set; set = set->next ) if( set->value && !( set->flags & SET_NOSAVE ) ) if( !xml_printf( fd, 1, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) goto write_error; for( acc = irc->b->accounts; acc; acc = acc->next ) { unsigned char *pass_cr; char *pass_b64; int pass_len; pass_len = arc_encode( acc->pass, strlen( acc->pass ), (unsigned char**) &pass_cr, irc->password, 12 ); pass_b64 = base64_encode( pass_cr, pass_len ); g_free( pass_cr ); if( !xml_printf( fd, 1, "<account protocol=\"%s\" handle=\"%s\" password=\"%s\" " "autoconnect=\"%d\" tag=\"%s\"", acc->prpl->name, acc->user, pass_b64, acc->auto_connect, acc->tag ) ) { g_free( pass_b64 ); goto write_error; } g_free( pass_b64 ); if( acc->server && acc->server[0] && !xml_printf( fd, 0, " server=\"%s\"", acc->server ) ) goto write_error; if( !xml_printf( fd, 0, ">\n" ) ) goto write_error; for( set = acc->set; set; set = set->next ) if( set->value && !( set->flags & ACC_SET_NOSAVE ) ) if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) goto write_error; /* This probably looks pretty strange. g_hash_table_foreach is quite a PITA already (but it can't get much better in C without using #define, I'm afraid), and since it doesn't seem to be possible to abort the foreach on write errors, so instead let's use the _find function and return TRUE on write errors. Which means, if we found something, there was an error. :-) */ if( g_hash_table_find( acc->nicks, xml_save_nick, & fd ) ) goto write_error; if( !xml_printf( fd, 1, "</account>\n" ) ) goto write_error; } for( l = irc->channels; l; l = l->next ) { irc_channel_t *ic = l->data; if( ic->flags & IRC_CHANNEL_TEMP ) continue; if( !xml_printf( fd, 1, "<channel name=\"%s\" type=\"%s\">\n", ic->name, set_getstr( &ic->set, "type" ) ) ) goto write_error; for( set = ic->set; set; set = set->next ) if( set->value && strcmp( set->key, "type" ) != 0 ) if( !xml_printf( fd, 2, "<setting name=\"%s\">%s</setting>\n", set->key, set->value ) ) goto write_error; if( !xml_printf( fd, 1, "</channel>\n" ) ) goto write_error; } if( !xml_printf( fd, 0, "</user>\n" ) ) goto write_error; fsync( fd ); close( fd ); path2 = g_strndup( path, strlen( path ) - 7 ); if( rename( path, path2 ) != 0 ) { irc_rootmsg( irc, "Error while renaming temporary configuration file." ); g_free( path2 ); unlink( path ); return STORAGE_OTHER_ERROR; } g_free( path2 ); return STORAGE_OK; write_error: g_free( pass_buf ); irc_rootmsg( irc, "Write error. Disk full?" ); close( fd ); return STORAGE_OTHER_ERROR; }
static void ipc_child_cmd_takeover( irc_t *irc, char **cmd ) { if( strcmp( cmd[1], "NO" ) == 0 ) { /* Master->New connection */ /* No takeover, finish the login. */ } else if( strcmp( cmd[1], "INIT" ) == 0 ) { /* Master->New connection */ if( !set_getbool( &irc->b->set, "allow_takeover" ) ) { ipc_child_cmd_takeover_no( irc ); return; } /* Offer to take over the old session, unless for some reason we're already logging into IM connections. */ if( irc->login_source_id != -1 ) query_add( irc, NULL, "You're already connected to this server. " "Would you like to take over this session?", ipc_child_cmd_takeover_yes, ipc_child_cmd_takeover_no, NULL, irc ); /* This one's going to connect to accounts, avoid that. */ b_event_remove( irc->login_source_id ); irc->login_source_id = -1; } else if( strcmp( cmd[1], "AUTH" ) == 0 ) { /* Master->Old connection */ if( irc->password && cmd[2] && cmd[3] && ipc_child_recv_fd != -1 && strcmp( irc->user->nick, cmd[2] ) == 0 && strcmp( irc->password, cmd[3] ) == 0 && set_getbool( &irc->b->set, "allow_takeover" ) ) { irc_switch_fd( irc, ipc_child_recv_fd ); irc_sync( irc ); irc_rootmsg( irc, "You've successfully taken over your old session" ); ipc_child_recv_fd = -1; ipc_to_master_str( "TAKEOVER DONE\r\n" ); } else { ipc_to_master_str( "TAKEOVER FAIL\r\n" ); } } else if( strcmp( cmd[1], "DONE" ) == 0 ) { /* Master->New connection (now taken over by old process) */ irc_free( irc ); } else if( strcmp( cmd[1], "FAIL" ) == 0 ) { /* Master->New connection */ irc_rootmsg( irc, "Could not take over old session" ); } }
static xt_status handle_account(struct xt_node *node, gpointer data) { struct xml_parsedata *xd = data; char *protocol, *handle, *server, *password = NULL, *autoconnect, *tag, *locked; char *pass_b64 = NULL; unsigned char *pass_cr = NULL; int pass_len, local = 0; struct prpl *prpl = NULL; account_t *acc; struct xt_node *c; handle = xt_find_attr(node, "handle"); pass_b64 = xt_find_attr(node, "password"); server = xt_find_attr(node, "server"); autoconnect = xt_find_attr(node, "autoconnect"); tag = xt_find_attr(node, "tag"); locked = xt_find_attr(node, "locked"); protocol = xt_find_attr(node, "protocol"); if (protocol) { prpl = find_protocol(protocol); if (!prpl) { irc_rootmsg(xd->irc, "Error loading user config: Protocol not found: `%s'", protocol); return XT_ABORT; } local = protocol_account_islocal(protocol); } if (!handle || !pass_b64 || !protocol || !prpl) { return XT_ABORT; } pass_len = base64_decode(pass_b64, (unsigned char **) &pass_cr); if (xd->irc->auth_backend) { password = g_strdup((char *)pass_cr); } else { pass_len = arc_decode(pass_cr, pass_len, &password, xd->given_pass); if (pass_len < 0) { g_free(pass_cr); g_free(password); return XT_ABORT; } } acc = account_add(xd->irc->b, prpl, handle, password); if (server) { set_setstr(&acc->set, "server", server); } if (autoconnect) { set_setstr(&acc->set, "auto_connect", autoconnect); } if (tag) { set_setstr(&acc->set, "tag", tag); } if (local) { acc->flags |= ACC_FLAG_LOCAL; } if (locked && !g_strcasecmp(locked, "true")) { acc->flags |= ACC_FLAG_LOCKED; } g_free(pass_cr); g_free(password); handle_settings(node, &acc->set); for (c = node->children; (c = xt_find_node(c, "buddy")); c = c->next) { char *handle, *nick; handle = xt_find_attr(c, "handle"); nick = xt_find_attr(c, "nick"); if (handle && nick) { nick_set_raw(acc, handle, nick); } else { return XT_ABORT; } } return XT_HANDLED; }
static storage_status_t xml_load_real(irc_t *irc, const char *my_nick, const char *password, xml_action action) { struct xml_parsedata xd[1]; char *fn, buf[2048]; int fd, st; struct xt_parser *xp = NULL; struct xt_node *node; storage_status_t ret = STORAGE_OTHER_ERROR; xd->irc = irc; strncpy(xd->given_nick, my_nick, MAX_NICK_LENGTH); xd->given_nick[MAX_NICK_LENGTH] = '\0'; nick_lc(NULL, xd->given_nick); xd->given_pass = (char *) password; fn = g_strconcat(global.conf->configdir, xd->given_nick, ".xml", NULL); if ((fd = open(fn, O_RDONLY)) < 0) { if (errno == ENOENT) { ret = STORAGE_NO_SUCH_USER; } else { irc_rootmsg(irc, "Error loading user config: %s", g_strerror(errno)); } goto error; } xp = xt_new(handlers, xd); while ((st = read(fd, buf, sizeof(buf))) > 0) { st = xt_feed(xp, buf, st); if (st != 1) { break; } } close(fd); if (st != 0) { goto error; } node = xp->root; if (node == NULL || node->next != NULL || strcmp(node->name, "user") != 0) { goto error; } if (action == XML_PASS_CHECK) { char *nick = xt_find_attr(node, "nick"); char *pass = xt_find_attr(node, "password"); char *backend = xt_find_attr(node, "auth_backend"); if (!nick || !(pass || backend)) { goto error; } if (backend) { g_free(xd->irc->auth_backend); xd->irc->auth_backend = g_strdup(backend); ret = STORAGE_CHECK_BACKEND; } else if ((st = md5_verify_password(xd->given_pass, pass)) != 0) { ret = STORAGE_INVALID_PASSWORD; } else { ret = STORAGE_OK; } goto error; } if (xt_handle(xp, NULL, 1) == XT_HANDLED) { ret = STORAGE_OK; } handle_settings(node, &xd->irc->b->set); error: xt_free(xp); g_free(fn); return ret; }
void irc_process(irc_t *irc) { char **lines, *temp, **cmd; int i; if (irc->readbuffer != NULL) { lines = irc_splitlines(irc->readbuffer); for (i = 0; *lines[i] != '\0'; i++) { char *conv = NULL; /* [WvG] If the last line isn't empty, it's an incomplete line and we should wait for the rest to come in before processing it. */ if (lines[i + 1] == NULL) { temp = g_strdup(lines[i]); g_free(irc->readbuffer); irc->readbuffer = temp; i++; break; } if (irc->iconv != (GIConv) - 1) { gsize bytes_read, bytes_written; conv = g_convert_with_iconv(lines[i], -1, irc->iconv, &bytes_read, &bytes_written, NULL); if (conv == NULL || bytes_read != strlen(lines[i])) { /* GLib can do strange things if things are not in the expected charset, so let's be a little bit paranoid here: */ if (irc->status & USTATUS_LOGGED_IN) { irc_rootmsg(irc, "Error: Charset mismatch detected. The charset " "setting is currently set to %s, so please make " "sure your IRC client will send and accept text in " "that charset, or tell BitlBee which charset to " "expect by changing the charset setting. See " "`help set charset' for more information. Your " "message was ignored.", set_getstr(&irc->b->set, "charset")); g_free(conv); conv = NULL; } else { irc_write(irc, ":%s NOTICE * :%s", irc->root->host, "Warning: invalid characters received at login time."); conv = g_strdup(lines[i]); for (temp = conv; *temp; temp++) { if (*temp & 0x80) { *temp = '?'; } } } } lines[i] = conv; } if (lines[i] && (cmd = irc_parse_line(lines[i]))) { irc_exec(irc, cmd); g_free(cmd); } g_free(conv); /* Shouldn't really happen, but just in case... */ if (!g_slist_find(irc_connection_list, irc)) { g_free(lines); return; } } if (lines[i] != NULL) { g_free(irc->readbuffer); irc->readbuffer = NULL; } g_free(lines); } }
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; }