/* bot-users.c : IRC bot plugin for irssi - user handling Copyright (C) 1999-2000 Timo Sirainen 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 along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE /* for crypt() */ #endif #include "module.h" #include "signals.h" #include "misc.h" #include "lib-config/iconfig.h" #include "channels.h" #include "irc-nicklist.h" #include "masks.h" #include "bot-users.h" #define WRITE_USERS_INTERVAL (60*15) static char *user_flags = "oavm"; /* Keep these in the same order as USER_xxx flags */ static CONFIG_REC *userconfig; static GHashTable *users; static int writeusers_tag; static time_t last_write; int botuser_flags2value(const char *flags) { char *pos; int val; g_return_val_if_fail(flags != NULL, 0); val = 0; while (*flags != '\0') { pos = strchr(user_flags, *flags); if (pos != NULL) val |= 1 << (int) (pos-user_flags); flags++; } return val; } char *botuser_value2flags(int value) { char *str, *p; int n; p = str = g_malloc(USER_FLAG_COUNT+1); for (n = 0; n < USER_FLAG_COUNT; n++) { if (value & (1 << n)) *p++ = user_flags[n]; } *p = '\0'; return str; } /* save channel specific user record */ static void botuser_save_chan(const char *key, USER_CHAN_REC *rec, CONFIG_NODE *node) { CONFIG_NODE *noderec; char *str; if (rec->flags == 0) { /* no flags in this channel - no need to save to config */ config_node_set_str(userconfig, node, rec->channel, NULL); return; } noderec = config_node_section(node, rec->channel, NODE_TYPE_BLOCK); str = botuser_value2flags(rec->flags); config_node_set_str(userconfig, noderec, "flags", str); g_free_not_null(str); } static void botuser_config_save(USER_REC *user) { CONFIG_NODE *node, *subnode, *noderec; GSList *tmp; char *str; user->last_modify = time(NULL); node = config_node_traverse(userconfig, "users", TRUE); node = config_node_section(node, user->nick, NODE_TYPE_BLOCK); str = user->flags == 0 ? NULL : botuser_value2flags(user->flags); config_node_set_str(userconfig, node, "flags", str); g_free_not_null(str); config_node_set_str(userconfig, node, "password", user->password); config_node_set_int(node, "last_modify", (int) user->last_modify); /* Save masks */ if (user->masks == NULL) config_node_set_str(userconfig, node, "masks", NULL); else { subnode = config_node_section(node, "masks", NODE_TYPE_LIST); for (tmp = user->masks; tmp != NULL; tmp = tmp->next) { USER_MASK_REC *rec = tmp->data; noderec = config_node_section(subnode, NULL, NODE_TYPE_BLOCK); config_node_set_str(userconfig, noderec, "mask", rec->mask); str = user->flags == 0 ? NULL : botuser_value2flags(rec->not_flags); config_node_set_str(userconfig, noderec, "not_flags", str); g_free_not_null(str); } } /* Save channels */ if (g_hash_table_size(user->channels) == 0) config_node_set_str(userconfig, node, "channels", NULL); else { subnode = config_node_section(node, "channels", NODE_TYPE_LIST); g_hash_table_foreach(user->channels, (GHFunc) botuser_save_chan, subnode); } } static int botuser_find_mask(USER_REC *user, const char *nick, const char *host) { GSList *tmp; g_return_val_if_fail(user != NULL, FALSE); g_return_val_if_fail(nick != NULL, FALSE); g_return_val_if_fail(host != NULL, FALSE); /* Check that masks match */ for (tmp = user->masks; tmp != NULL; tmp = tmp->next) { USER_MASK_REC *rec = tmp->data; if (mask_match_address(NULL, rec->mask, nick, host)) { user->not_flags = rec->not_flags; return TRUE; } } return FALSE; } static USER_MASK_REC *botuser_find_full_mask(USER_REC *user, const char *mask) { GSList *tmp; g_return_val_if_fail(user != NULL, FALSE); g_return_val_if_fail(mask != NULL, FALSE); for (tmp = user->masks; tmp != NULL; tmp = tmp->next) { USER_MASK_REC *rec = tmp->data; if (g_strcasecmp(rec->mask, mask) == 0) return rec; } return NULL; } static void botuser_getusers_hash(void *key, USER_REC *user, GList **list) { *list = g_list_append(*list, user); } USER_REC *botuser_find(const char *nick, const char *host) { USER_REC *user; char *stripnick; GList *list, *tmp; g_return_val_if_fail(nick != NULL, NULL); /* First check for user with same nick */ stripnick = irc_nick_strip(nick); user = g_hash_table_lookup(users, stripnick); g_free(stripnick); if (user != NULL && host != NULL && !botuser_find_mask(user, nick, host)) { /* mask didn't match, check for more.. */ user = NULL; } if (user != NULL || host == NULL) return user; /* Check for different nicks.. */ list = NULL; g_hash_table_foreach(users, (GHFunc) botuser_getusers_hash, &list); for (tmp = list; tmp != NULL; tmp = tmp->next) { if (botuser_find_mask(tmp->data, nick, host)) { user = tmp->data; break; } } g_list_free(list); return user; } USER_REC *botuser_find_rec(CHANNEL_REC *channel, NICK_REC *nick) { USER_REC *user, *rec; USER_CHAN_REC *userchan; GList *list, *tmp; g_return_val_if_fail(channel != NULL, NULL); g_return_val_if_fail(nick != NULL, NULL); user = NULL; list = NULL; g_hash_table_foreach(users, (GHFunc) botuser_getusers_hash, &list); for (tmp = list; tmp != NULL; tmp = tmp->next) { rec = tmp->data; userchan = g_hash_table_lookup(rec->channels, channel->name); if (userchan != NULL && userchan->nickrec == nick) { user = rec; break; } } g_list_free(list); return user; } USER_CHAN_REC *botuser_get_channel(USER_REC *user, const char *channel) { USER_CHAN_REC *rec; g_return_val_if_fail(user != NULL, NULL); g_return_val_if_fail(channel != NULL, NULL); rec = g_hash_table_lookup(user->channels, channel); if (rec != NULL) return rec; rec = g_new0(USER_CHAN_REC, 1); rec->channel = g_strdup(channel); g_hash_table_insert(user->channels, rec->channel, rec); return rec; } USER_REC *botuser_add(const char *nick) { USER_REC *user; /* Add new user */ user = g_new0(USER_REC, 1); user->nick = g_strdup(nick); g_hash_table_insert(users, user->nick, user); botuser_config_save(user); return user; } void botuser_set_flags(USER_REC *user, int flags) { user->flags = flags; botuser_config_save(user); } void botuser_set_channel_flags(USER_REC *user, const char *channel, int flags) { USER_CHAN_REC *rec; rec = botuser_get_channel(user, channel); if (rec != NULL) rec->flags = flags; botuser_config_save(user); } static USER_MASK_REC *botuser_create_mask(USER_REC *user, const char *mask) { USER_MASK_REC *rec; rec = g_new0(USER_MASK_REC, 1); rec->mask = g_strdup(mask); user->masks = g_slist_append(user->masks, rec); return rec; } USER_MASK_REC *botuser_add_mask(USER_REC *user, const char *mask) { USER_MASK_REC *rec; rec = botuser_create_mask(user, mask); botuser_config_save(user); return rec; } void botuser_set_mask_notflags(USER_REC *user, const char *mask, int not_flags) { USER_MASK_REC *rec; rec = botuser_find_full_mask(user, mask); if (rec == NULL) rec = botuser_create_mask(user, mask); rec->not_flags = not_flags; botuser_config_save(user); } void botuser_set_password(USER_REC *user, const char *password) { char *pass, salt[3]; g_return_if_fail(user != NULL); g_return_if_fail(password != NULL); salt[0] = rand()%20 + 'A'; salt[1] = rand()%20 + 'A'; salt[2] = '\0'; pass = crypt(password, salt); if (user->password != NULL) g_free(user->password); user->password = g_strdup(pass); botuser_config_save(user); } int botuser_verify_password(USER_REC *user, const char *password) { char *pass, salt[3]; g_return_val_if_fail(user != NULL, FALSE); g_return_val_if_fail(password != NULL, FALSE); if (user->password == NULL || strlen(user->password) < 3) return FALSE; salt[0] = user->password[0]; salt[1] = user->password[1]; salt[2] = '\0'; pass = crypt(password, salt); return strcmp(user->password, pass) == 0; } void botuser_save(const char *fname) { config_write(userconfig, fname, 0600); } static void event_massjoin(CHANNEL_REC *channel, GSList *nicks) { USER_REC *user; USER_CHAN_REC *userchan; GSList *users; g_return_if_fail(channel != NULL); g_return_if_fail(nicks != NULL); users = NULL; for (; nicks != NULL; nicks = nicks->next) { NICK_REC *rec = nicks->data; user = botuser_find(rec->nick, rec->host); if (user != NULL) { userchan = botuser_get_channel(user, channel->name); userchan->nickrec = rec; users = g_slist_append(users, user); } } if (users != NULL) { signal_emit("bot massjoin", 2, channel, users); g_slist_free(users); } } /* channel synced - find everyone's NICK_REC's */ static void sig_channel_sync(CHANNEL_REC *channel) { USER_REC *user; USER_CHAN_REC *userchan; GSList *tmp, *nicks; g_return_if_fail(channel != NULL); nicks = nicklist_getnicks(channel); for (tmp = nicks; tmp != NULL; tmp = tmp->next) { NICK_REC *rec = tmp->data; if (rec->send_massjoin) continue; /* This will be checked in "massjoin" signal */ user = botuser_find(rec->nick, rec->host); if (user != NULL) { userchan = botuser_get_channel(user, channel->name); userchan->nickrec = rec; } } g_slist_free(nicks); } /* user left channel - remove from users record */ static void sig_nicklist_remove(CHANNEL_REC *channel, NICK_REC *nick) { USER_REC *user; USER_CHAN_REC *userchan; g_return_if_fail(channel != NULL); g_return_if_fail(nick != NULL); user = botuser_find_rec(channel, nick); userchan = user == NULL ? NULL : g_hash_table_lookup(user->channels, channel->name); if (userchan != NULL) userchan->nickrec = NULL; } /* Free memory used by user channel record */ static void user_destroy_chan(const char *key, USER_CHAN_REC *rec) { g_free(rec->channel); g_free(rec); } static void usermask_destroy(USER_MASK_REC *rec) { g_free(rec->mask); g_free(rec); } /* Free memory used by user record */ static void user_destroy(const char *key, USER_REC *user) { g_slist_foreach(user->masks, (GFunc) usermask_destroy, NULL); g_slist_free(user->masks); g_hash_table_foreach(user->channels, (GHFunc) user_destroy_chan, NULL); g_hash_table_destroy(user->channels); g_free_not_null(user->password); g_free(user->nick); g_free(user); } static int sig_write_users(void) { if (last_write + WRITE_USERS_INTERVAL <= time(NULL)) { last_write = time(NULL); config_write(userconfig, NULL, -1); } return 1; } static void botuser_config_read_user(CONFIG_NODE *node) { USER_REC *user; USER_CHAN_REC *userchan; USER_MASK_REC *usermask; CONFIG_NODE *subnode; GSList *tmp; char *value; g_return_if_fail(node != NULL); /* nick = { ... } */ if (node->key == NULL || node->value == NULL) return; /* Add new user */ user = g_new0(USER_REC, 1); user->nick = g_strdup(node->key); g_hash_table_insert(users, user->nick, user); /* password, flags, modify time */ user->password = g_strdup(config_node_get_str(node, "password", NULL)); user->flags = botuser_flags2value(config_node_get_str(node, "flags", "")); user->last_modify = (time_t) config_node_get_int(node, "last_modify", 0); /* get masks */ user->masks = NULL; subnode = config_node_section(node, "masks", -1); tmp = subnode == NULL ? NULL : subnode->value; for (; tmp != NULL; tmp = tmp->next) { subnode = tmp->data; value = config_node_get_str(subnode, "mask", NULL); if (value == NULL) continue; /* mask is required */ usermask = botuser_create_mask(user, value); value = config_node_get_str(subnode, "not_flags", ""); usermask->not_flags = botuser_flags2value(value); } /* get channels - must be last, messes up pvalue */ user->channels = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); subnode = config_node_section(node, "channels", -1); tmp = subnode == NULL ? NULL : subnode->value; for (; tmp != NULL; tmp = tmp->next) { subnode = tmp->data; value = config_node_get_str(subnode, "channel", NULL); if (value == NULL) continue; /* channel is required */ /* create user channel specific record */ userchan = g_new0(USER_CHAN_REC, 1); userchan->channel = g_strdup(value); g_hash_table_insert(user->channels, userchan->channel, userchan); value = config_node_get_str(subnode, "flags", ""); userchan->flags = botuser_flags2value(value); } } static void botuser_config_read(void) { CONFIG_NODE *node; GSList *tmp; char *fname; /* Read users from ~/.irssi/users */ fname = g_strdup_printf("%s/.irssi/users", g_get_home_dir()); userconfig = config_open(fname, 0600); g_free(fname); if (userconfig == NULL) return; /* access denied?! */ config_parse(userconfig); node = config_node_traverse(userconfig, "users", FALSE); tmp = node == NULL ? NULL : node->value; for (; tmp != NULL; tmp = tmp->next) botuser_config_read_user(tmp->data); } void bot_users_init(void) { users = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal); last_write = time(NULL); writeusers_tag = g_timeout_add(10000, (GSourceFunc) sig_write_users, NULL); botuser_config_read(); signal_add_last("massjoin", (SIGNAL_FUNC) event_massjoin); signal_add_last("channel sync", (SIGNAL_FUNC) sig_channel_sync); signal_add_last("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove); } void bot_users_deinit(void) { if (userconfig != NULL) { config_write(userconfig, NULL, -1); config_close(userconfig); } g_source_remove(writeusers_tag); g_hash_table_foreach(users, (GHFunc) user_destroy, NULL); g_hash_table_destroy(users); signal_remove("massjoin", (SIGNAL_FUNC) event_massjoin); signal_remove("channel sync", (SIGNAL_FUNC) sig_channel_sync); signal_remove("nicklist remove", (SIGNAL_FUNC) sig_nicklist_remove); }