/* IRCfs - IRC FileServ for *nix. * Copyright (C) 2002 Nick 'Zaf' Clifford * For licensing details, refer to the LICENSE file in the source * code directory. */ #include "module.h" #include "user.h" #include "server.h" #include "list.h" #include "select.h" #include "debug.h" #include "channel.h" #include "magic.h" #define MAGIC_USER 0x77528853 #define MAGIC_USER_SERVER_DATA 0x81371531 struct user_server_data { MAGIC magic; struct list *user_list; // struct user list }; struct user { MAGIC magic; POOL pool; char *nick; char *username; char *hostname; char *info; enum user_status status; int grabbed; struct server *server; struct user_server_data *usd; // struct list *channels; // string list UNUSED }; static void user_remove_server(struct user *u); static void user_destroy(struct user *u); static int user_event_filter(struct eventmsg *msg, void *filter); static int user_cleanup_server(struct server *s); static void user_start_cleanup(); static int user_new_server(struct eventmsg *msg); static int user_server_dies(struct eventmsg *msg); void user_dump_channel(const char *name); static int user_each_server(struct server *s); static int user_command_quit(struct server *s, const char *command, int argc, char *argv[], void *appdata); static int user_command_privmsg(struct server *s, const char *command, int argc, char *argv[], void *appdata); static int user_command_nick(struct server *s, const char *command, int argc, char *argv[], void *appdata); static int user_code_311(struct server *s, int code, int argc, char *argv[], void *appdata); static int user_code_312(struct server *s, int code, int argc, char *argv[], void *appdata); static int user_code_317(struct server *s, int code, int argc, char *argv[], void *appdata); static int user_code_318(struct server *s, int code, int argc, char *argv[], void *appdata); static int user_code_319(struct server *s, int code, int argc, char *argv[], void *appdata); MODULE_NAME("user"); MODULE_INIT(user_init); MODULE_DESTROY(user_unload); MODULE_DEPENDS("server","select"); MODULE_REGISTER(user); DEBUG user_debug; struct eventobj *user_eventobj; int user_init() { user_debug = debug_register("user"); user_eventobj = event_create_obj(NULL,NULL); event_set_filter_func(user_eventobj,user_event_filter); server_foreach(user_each_server); event_add_listener(server_get_event_obj(), server_event_new, NULL, user_new_server, NULL); register_post_func(user_start_cleanup); return 0; } int user_unload() { return -1; } struct eventobj *user_get_event_obj() { return user_eventobj; } int user_cleanup_server(struct server *s) { struct user_server_data *usd; usd = server_get_module_data(s,"user"); if (usd) { ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); list_cleanup(usd->user_list); } return 0; } void user_start_cleanup() { server_foreach(user_cleanup_server); } int user_cleanup(struct list *l, struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); debug(user_debug,"user_cleanup(%s)", u->nick); pool_destroy(u->pool); return 0; } int user_raise_event(struct user *u, enum user_events event, char *txt) { struct user_event_msg msg; set_magic(&msg,MAGIC_USER_EVENT_MSG); msg.user = u; msg.server = u->server; msg.txt = txt; ASSERT(is_magic(u,MAGIC_USER)); return event_raise(user_eventobj, event, &msg); } static int user_event_filter(struct eventmsg *msg, void *filter) { struct user *u; struct user_event_msg *uem; u = (struct user *) filter; uem = msg->msg; ASSERT(is_magic(u,MAGIC_USER)); ASSERT(is_magic(uem,MAGIC_USER_EVENT_MSG)); if (u == uem->user) { return 0; } return -1; } struct user *user_grab(struct user *u) { u->grabbed++; debug(user_debug,"User(%s) grabbed! (%d times now)", u->nick, u->grabbed); return u; } void user_release(struct user *u) { ASSERT(u); u->grabbed--; ASSERT(u->grabbed >= 0); debug(user_debug,"User(%s) release (now %d)", u->nick, u->grabbed); if (u->grabbed == 0 && u->status == user_status_dead) { user_destroy(u); } } struct user *user_find(struct server *s, const char *nick) { struct user *u; struct user_server_data *usd; usd = server_get_module_data(s,"user"); if (!usd) { return NULL; } ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); u = list_find(usd->user_list,(void *)nick, (list_compare_t)user_compare_byname); if (u) { ASSERT(is_magic(u,MAGIC_USER)); /* User exists, but is dead, so we return NULL. * If a user with this nick has come back on, then * when user_create is called, this double entry will * be taken care of */ if (u->status == user_status_dead) { return NULL; } if (index(nick, '!') != NULL) { /* Oooh, we have more info, lets try and update * our info */ user_update_info(u, nick); } } return u; } /* Update the user record with the user@host part of the name. * name must be in nick!user@host format */ int user_update_info(struct user *u, const char *name) { const char *cp; char *nextc, *c; ASSERT(is_magic(u, MAGIC_USER)); cp = name; while(*cp != '!' && *cp != 0) { cp++; } if (*cp == 0) return -1; cp++; nextc = NULL; if (u->username == NULL || *u->username == 0) { /* set */ u->username = pstrdup_portion(u->pool, cp, '@', &nextc); } else { /* compare */ if (strcasecmp_portion(u->username, cp, '@', &nextc) != 0) { c = pstrdup_portion(u->pool, cp, '@', &nextc); warn(user_debug,"User(%s)'s username has changed " "from %s to %s! This isn't normal", u->nick, u->username, cp); u->username = c; } } if (nextc == NULL) return -1; cp = nextc; if (*cp == 0) return -1; cp++; if (u->hostname == NULL || *u->hostname == 0) { /* set */ u->hostname = pstrdup(u->pool, cp); } else { /* compare */ if (strcasecmp(u->hostname, cp) != 0) { c = pstrdup(u->pool, cp); warn(user_debug,"User(%s)'s hostname has changed " "from %s to %s! This isn't normal!", u->nick, u->hostname, cp); u->hostname = c; } } return 0; } int user_server_dies(struct eventmsg *msg) { struct server *s; struct user_server_data *usd; /* Remove each of the users from the user list, * posting their departure as we go */ s = msg->msg; usd = server_get_module_data(s,"user"); ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); list_foreach_simple(usd->user_list, (list_simple_callback_t)user_remove_server); return 0; } void user_remove_server(struct user *u) { user_destroy(u); u->server = NULL; return; } void user_dump_list(struct server *s) { struct user_server_data *usd; usd = server_get_module_data(s,"user"); ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); debug(user_debug,"User list on server %s", server_get_name(s)); list_foreach_simple(usd->user_list, (list_simple_callback_t)user_dump_user); debug(user_debug,"End of user list"); return; } void user_dump_user(struct user *u) { debug(user_debug,"User %s (%s@%s) Info: %s", u->nick, u->username, u->hostname, u->info); debug(user_debug," Status: %s (grabbed %d)", u->status == user_status_alive ? "alive" : "dead", u->grabbed); debug(user_debug," Server %s", server_get_name(u->server)); // debug(user_debug," Channel List: "); // list_foreach_simple(u->channels, // (list_simple_callback_t)user_dump_channel); debug(user_debug," End"); } void user_dump_channel(const char *name) { debug(user_debug," %s", name); } int user_new_server(struct eventmsg *msg) { struct server *s = (struct server *)msg->msg; return user_each_server(s); } int user_each_server(struct server *s) { struct user_server_data *usd; POOL p; p = server_get_pool(s); usd = XMALLOC(p,struct user_server_data); set_magic(usd,MAGIC_USER_SERVER_DATA); usd->user_list = list_create(p); server_set_module_data(s,"user",usd); list_delete_func(usd->user_list, (list_delete_func_t)user_cleanup); server_set_command_handler(s,"QUIT",0,NULL,user_command_quit,NULL); server_set_command_handler(s,"PRIVMSG",2,SERVER_TRIGGER_MYNICK, user_command_privmsg,NULL); server_set_command_handler(s,"NOTICE",2,SERVER_TRIGGER_MYNICK, user_command_privmsg,NULL); server_set_command_handler(s,"NICK",0,NULL, user_command_nick,NULL); server_set_code_handler(s,311,0,NULL,user_code_311,NULL); server_set_code_handler(s,312,0,NULL,user_code_312,NULL); server_set_code_handler(s,317,0,NULL,user_code_317,NULL); server_set_code_handler(s,318,0,NULL,user_code_318,NULL); server_set_code_handler(s,319,0,NULL,user_code_319,NULL); event_add_listener(server_get_event_obj(), server_event_disconnected, s, user_server_dies, NULL); return 0; } static int user_command_nick(struct server *s, const char *command, int argc, char *argv[], void *appdata) { struct user *u, *utmp; struct user_server_data *usd; if (!argv[0]) return 0; usd = server_get_module_data(s,"user"); ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); u = list_find(usd->user_list,argv[0], (list_compare_t)user_compare_byname); if (!u) u = user_create(s,argv[0]); ASSERT(u); user_update_info(u, argv[0]); debug(user_debug,"User %s on server %s changed nick to %s", u->nick, server_get_name(s), argv[2]); utmp = list_find(usd->user_list, argv[2], (list_compare_t)user_compare_byname); if (utmp) { notice(user_debug,"Hmm, user %s on server %s is changing " "nick to %s, however, I already have a " "user named %s on this server, destroying " "the existing (old(?)) record", u->nick, server_get_name(s), argv[2], utmp->nick); user_destroy(utmp); } u->nick = pstrdup(u->pool, argv[2]); user_raise_event(u,user_event_changed_nick,argv[2]); return 0; } static int user_command_privmsg(struct server *s, const char *command, int argc, char *argv[], void *appdata) { struct user *u; struct user_server_data *usd; int notice = 0; if (!argv[0]) return 0; /* Ignore messages to channels */ if (channel_is_prefix(*argv[2]) == 0) return 0; if (*command == 'N' || *command == 'n') notice = 1; usd = server_get_module_data(s,"user"); ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); u = list_find(usd->user_list,argv[0], (list_compare_t)user_compare_byname); if (!u) u = user_create(s,argv[0]); ASSERT(u); user_update_info(u, argv[0]); debug(user_debug,"User %s on server %s %s: %s", u->nick, server_get_name(s), notice ? "notice'd" : "said", argv[3]); user_raise_event(u,notice ? user_event_notice : user_event_msg,argv[3]); return 0; } struct user *user_create(struct server *server, const char *name) { struct user_server_data *usd; struct user *u; char *cp,*next; int do_create = 1; POOL p; usd = server_get_module_data(server,"user"); ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); u = list_find(usd->user_list,(void *)name, (list_compare_t)user_compare_byname); if (u != NULL) { ASSERT(is_magic(u,MAGIC_USER)); if (user_compare_exact(name, u) == 0) { info(user_debug,"It seems %s has reconnected", name); do_create = 0; } else { info(user_debug,"Found previous user %s, destroying", name); //list_mark_current(usd->user_list); user_destroy(u); } } if (do_create) { p = pool_new(server_get_pool(server)); u = XMALLOC(p,struct user); set_magic(u,MAGIC_USER); u->pool = p; } else { /* Nothing for now */ /* This is where we would do the code to reinitialize * a user record if the user reappears * Of course, other modules need to realize that even * if they call user_destroy, the account may not be * destroyed, infact, it could suddenly come back to life! * So modules need to remove listeners to the user module * if they don't want anything to do with that user, and * not rely on the user_destroy removing them for them. */ } u->nick = pstrdup(u->pool,name); if ((cp=strchr(u->nick,'!'))) { *cp = 0; next = strchr(cp+1,'@'); if (next) { *next = 0; } u->username = pstrdup(u->pool,cp+1); if (next) { u->hostname = pstrdup(u->pool,next+1); } } u->status = user_status_alive; u->server = server; u->usd = usd; debug(user_debug,"%s user %s on server %s", do_create ? "Created" : "Reinitialized", u->nick,server_get_name(server)); if (index(name, '!') != NULL) { /* Oooh, we have more info, lets try and update * our info */ user_update_info(u, name); } if (do_create) { list_add_end(usd->user_list, u); } return u; } const char *user_get_name(struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); return u->nick; } const char *user_get_nick(struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); return u->nick; } const char *user_get_username(struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); return u->username; } const char *user_get_hostname(struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); return u->hostname; } const char *user_get_info(struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); return u->info; } struct server *user_get_server(struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); return u->server; } /* int user_compare_exact(name, user) * * Compares if the specified name (in either nick, or nick!user@host format) * matches the specifed user. * * If the name is in nick format, or the user doesn't have the user and host * fields, function will return 1 for a match, -1 for failure. * * If the name is in the nick!user@host format, function will return 0 for * a match, and -1 for failure * */ int user_compare_exact(const char *name, struct user *user) { const char *a, *b; ASSERT(is_magic(user,MAGIC_USER)); a = name; b = user->nick; while(*a != '!' && *a != 0) { if (*a != *b) return -1; a++; b++; } /* Ok, so the nick matches, is it a full name? (eg nick!user@host) */ if (*a == 0) { /* Nope */ return 1; } a++; b = user->username; if (b == NULL || *b == 0) return 1; while(*a != '@' && *a != 0) { if (*a != *b) return -1; a++; b++; } if (*a != 0) a++; b = user->hostname; if (b == NULL || *b == 0) return 1; while(*a != 0) { if (*a != *b) return -1; a++; b++; } return 0; } int user_compare_byname(const char *nick, struct user *u) { const char *cp,*dp; ASSERT(is_magic(u,MAGIC_USER)); if (!nick || !u) return -1; for(cp = nick, dp=u->nick; *cp != 0 && *dp != 0 && *cp != '!'; cp++, dp++) { if (tolower(*cp) != tolower(*dp)) return -1; } if ((*cp == '!' || *cp == 0) && *dp == 0) return 0; return -1; } int user_command_quit(struct server *s, const char *command, int argc, char *argv[], void *appdata) { struct user *u; struct user_server_data *usd; if (!argv[0]) return 0; usd = server_get_module_data(s,"user"); ASSERT(is_magic(usd,MAGIC_USER_SERVER_DATA)); u = list_find(usd->user_list,argv[0], (list_compare_t)user_compare_byname); if (!u) return 0; ASSERT(is_magic(u,MAGIC_USER)); user_update_info(u, argv[0]); user_raise_event(u,user_event_quit,""); debug(user_debug,"User(%s) has quit. %s", u->nick, argv[argc]); user_destroy(u); return 0; } void user_destroy(struct user *u) { ASSERT(is_magic(u,MAGIC_USER)); debug(user_debug,"User(%s) destroying", u->nick); if (u->grabbed > 0) { debug(user_debug,"User(%s) destroy, but grabbed %d times " "so won't destroy yet.", u->nick, u->grabbed); u->status = user_status_dead; return; } user_raise_event(u,user_event_destroy,""); if (list_find_data(u->usd->user_list,u)) list_mark_current(u->usd->user_list); } int user_code_319(struct server *s, int code, int argc, char *argv[], void *appdata) { struct user *u; /* [0|localhost [1|319] [2|ME] [3|NICK] [4|CHANS] */ u = user_find(s,argv[3]); if (!u) return 0; debug(user_debug,"User(%s) on channels %s", u->nick, argv[4]); return 0; } int user_code_311(struct server *s, int code, int argc, char *argv[], void *ad) { struct user *u; /* [0|localhost] [1|311] [2|ME] [3|NICK] [4|USERNAME] [5|HOST] [6|*] [7|INFO] */ if (argc < 7) return 0; u = user_find(s,argv[3]); if (!u) { u = user_create(s,argv[3]); } if (!u->info || strcmp(u->info,argv[7]) != 0) { u->info = pstrdup(u->pool,argv[7]); } if (!u->username || strcmp(u->username,argv[4]) != 0) { u->username = pstrdup(u->pool,argv[4]); } if (!u->hostname || strcmp(u->hostname,argv[5]) != 0) { u->hostname = pstrdup(u->pool,argv[5]); } debug(user_debug,"Updated info for %s(%s@%s)-%s", u->nick, u->username, u->hostname, u->info); return 0; } int user_code_312(struct server *s, int code, int argc, char *argv[], void *ad) { struct user *u; /* [0|localhost] [1|312] [2|ME] [3|NICK] [4|SERVER] [5|SERVERDESC] */ u = user_find(s,argv[3]); if (!u) return 0; debug(user_debug,"User(%s) server %s", u->nick, argv[4]); return 0; } int user_code_317(struct server *s, int code, int argc, char *argv[], void *ad) { struct user *u; /* [0|localhost] [1|317] [2|ME] [3|NICK] [4|IDLE] [5|ONTIME] [6|text] */ u = user_find(s,argv[3]); if (!u) return 0; debug(user_debug,"User(%s) idle %s secs", u->nick, argv[4]); return 0; } int user_code_318(struct server *s, int code, int argc, char *argv[], void *ad) { /* [0|localhost] [1|318] [2|ME] [3|NICK] [4|End of /WHOIS] */ return 0; }