#define AUTHOR "Scott 'Simba' Garron " #define VERSION "1.3.0" #define MODNAME "ns_ajoin" /*----------------------------------------------------------------------- * Name: ns_ajoin * Author: Scott 'Simba' Garron * Date: 2005-03-06 * Version: 1.3.0 * * Adapted from: parts of unreal_addon by Jens 'DukePyrolator' Voss * * Description: Gives the user tools to manage lists of channels to which * he/she will be automatically joined when they /nickserv update or * /nickserv identify. * * Beyond the functionality of most autojoin implementations, this one * also interacts directly with ChanServ to get channel keys and invites * when needed before performing the SVSJOIN. * *----------------------------------------------------------------------- * Supported IRCD: Any IRCd which supports SVSJOIN * Tested with: Unreal 3.2.2b and Anope 1.7.8 (600) * Requires: MySQL *----------------------------------------------------------------------- * new commands: * * - /ns AJOIN * Add, delete, group add, group merge, group copy, group replace, * list or clear your autojoin channels * * - /ns SET AJOIN * Turn automatic channel join on, off, or on silent. * * This module interacts with the MySQL data that it uses in realtime, * which means that other interfaces can be made (ie. web) and the * changes made in those interfaces will be instantly used by the module. * * This module has only been tested with UnrealIRCd. IRCds that * appear to support SVSJOIN have been added to the initial runtime * check. I make no guarantees, though. If you believe that an * Anope-supported IRCd supports SVSJOIN and ns_ajoin won't run * because of an error stating that it doesn't support SVSJOIN, let me * know. * *----------------------------------------------------------------------- * * Consistently updated versions of all of my modules can be found * at http://www.cimeris.com/~simba/anopemodules/ * * If you find a bug, please send me a Mail or contact me on * irc.anthrochat.net #AnthroChat * * ---------------------------------------------------------------------- * Change log: * v1.3.0 2005-03-06 - Added support for IRCds other than Unreal which * have SVSJOIN capability. * v1.2.10 2005-02-26 - Apparently some versions of MySQL treat a trailing * semi-colon as a syntax error even though it's proper SQL syntax. This * version of ns_ajoin now has no semi-colons in its SQL queries. * v1.2.9 2005-02-25 - do_on_identify() modified so that it doesn't * attempt to join a user to a channel if they're already on the channel. * v1.2.8 2005-02-25 - Added extra logging for debugging and a new * callback function for keeping the connection to the SQL server alive. * v1.2.7 2005-02-21 - Made to compile in Windows. * v1.2.6 2005-02-21 - Fixed a small glitch which caused compile errors * in gcc 2.x. Earlier versions of gcc didn't like doing variable * or sub-function declarations outside of the initial declaration block. * v1.2.5 2005-02-15 - notice_user_ww() was hardcoded to send from * s_NickServ. Changed it to use the "source" parameter. * v1.2.4 2005-02-08 - Fixed a logic bug in notice_user_ww(). * v1.2.3 2005-02-03 - Re-worked mydb_init() and purgedb() to reduce * effort duplication. * v1.2.2 2005-02-03 - Fixed a small memory leak in db_add_ajoin_info() * where a temporary string was being generated to store an sql-escaped * copy of another string and not being free()d after executing the * query. * v1.2.1 2005-01-29 - Re-wrote get_group_ajoin_lists() because it was * way too long and was doing way too much work for such a simple * task. * v1.2.0 2005-01-29 - Optimized, stable release. No major changes or * bug fixes, just a bunch of code optimization and comments. * v1.1.0 2005-01-28 - Finished ns_ajoin_replace(), which wraps up all * of the initial development for the module. * v1.0.10 2005-01-28 - Finished ns_ajoin_copy(). * v1.0.9 2005-01-27 - Finished ns_ajoin_clear(). * v1.0.8 2005-01-27 - Finished ns_ajoin_merge() and cleaned a copule of * memory leaks. * v1.0.7 2005-01-26 - Added checks for valid characters in channels names * (the same checks that are in the IRCd) * v1.0.6 2005-01-25 - Put purgedb() back in as a callback so that the * module will start doing its own clean-up, should nicknames expire * or be dropped. * v1.0.5 2005-01-25 - Fixed another bug introduced by Harik's * db_change_group_keys() patch. * v1.0.4 2005-01-25 - Fixed segfault bug in ns_ajoin_add(), same bug * as in v1.0.3, but in a different function. These bugs were introduced * when Harik changed the way that the structure data is passed between * the functions. He removed the lines that populate the "key" in an * AjoinChanList structure with a valid pointer, and they get passed * and acted upon with a bad pointer. * v1.0.3 2005-01-25 - Fixed segfault bug in ns_ajoin_gadd() hwere * blank keys were being generated if the user didn't specify one, but * the channel structure wasn't being populated with that string. * v1.0.2 2005-01-25 - Made bit names something that's human readable, * fixed bug in do_on_identify() where channels which weren't * registered weren't being ajoined. Some other misc cleanups. * v1.0.1 2005-01-25 - Fixed typo causing a segfault in channel_add_numsign() * v1.0.0 2005-01-21 - first release *----------------------------------------------------------------------- * Todo list: (feature ideas for newer versions) * - Come up with a way to determine the actual module's .so file name * at runtime in case we decide to rename it later * - Instead of hardcoding "15" channels as the maximum in the user's * list, come up with a way to query the IRCd's maximum channels * that a user can be in at once, and use that number instead. */ /* Max ajoin channels: Note const int so it can be changed elsewhere. */ const int MAX_AJOIN_CHANNELS = 15; /* Sets the delay between checks for expired/dropped nicks */ #define PURGETIME "2h" /* Sets the maximum number of characters that will be displayed on a * line before doing a word wrap when being displayed to the user */ #define LINELENGTHMAX 58 /* Language text definitions */ #define SYNTAX_WORD "Syntax: " #define SYNTAX_INDENT " " #define HELP_USAGE_HEAD "Type \002/msg %s HELP " #define HELP_USAGE_TAIL "\002 for more information." #define NOTICE_HELP_USAGE HELP_USAGE_HEAD "AJOIN %s" HELP_USAGE_TAIL #define MAIN_HELP_SUMMARY_AJOIN "AJOIN Maintain your autojoin list" #define HELP_AJOIN "\002AJOIN\002 Maintains a list of channels to which\ you will be automatically joined.\n\ \nAJOIN is performed on IDENTIFY if you have \002SET AJOIN\002 to\ \002ON\002 (or SILENT) or if you use NickServ's UPDATE command.\n\ \nA separate AJOIN list may be maintained for each nickname in your\ group\n\ \nAJOIN lists may hold no more than %u entries per nickname." #define AJOIN_ADD_SYNTAX "\002AJOIN %s \037channel\037 [\037key\037]" #define HELP_AJOIN_ADD "Adds a channel to %s's autojoin list.\ \nIf the channel is +k (password/key required to join), you may also\ specify the key when adding the channel to your list. If you need\ to change the key associated with the channel, simply re-issue\ the ADD command with the new key.\n\ \nWhen a channel with a key is added to your AJOIN list and that\ channel is also in any of your other grouped nicknames' AJOIN lists,\ the key will be updated in all lists.\n\ \nAlternatively, if you have access to ChanServ GETKEY for the channel\ in which you're adding, AJOIN will automatically update the key in\ your lists with the current key. If you don't have access to GETKEY\ but do have access to INVITE, an invite will be issued from Chanserv\ before the join.\n\ \nIf a channel in your list is set +i (invite only) and you have ChanServ\ INVITE access to that channel, an INVITE will automatically be issued\ from ChanServ before the join." #define AJOIN_DEL_SYNTAX "\002AJOIN DEL \037channel" #define HELP_AJOIN_DEL "Removes a channel from %s's autojoin list." #define HELP_AJOIN_GADD "Adds a channel to the autojoin list for all of\ the nicknames in your group." #define AJOIN_GDEL_SYNTAX "\002AJOIN DEL \037channel\037 [\037key\037]" #define HELP_AJOIN_GDEL "Removes a channel from the autojoin list for all\ of the nicknames in your group in which the channel exists." #define AJOIN_MERGE_SYNTAX "\002AJOIN MERGE [\037destination\037]" #define HELP_AJOIN_MERGE "MERGE will cross-copy all of your grouped\ nicknames' autojoin lists and make them all the same. If a\ destination nickname is specified, MERGE will replace that nickname's\ list with a cross-copy of all of your other nicknames' lists.\n\ \nA successful merge can only be accomplished if the total number of\ unique channels is less than or equal to %i." #define AJOIN_CP_REPL_SYNTAX "\002AJOIN %s [(\037source\037)\ \037destination\037]" #define HELP_AJOIN_COPY "If issued with no arguments, COPY will\ add the channels from %s's autojoin list to the lists of all of\ the nicknames in the same group.\n\ \nIf one nickname is specified, %s's list will be copied into the specified\ destination.\n\ \nIf two nicknames are specified, the first nickname's (source) list will\ be copied into the second's (destination).\n\ \nNo duplicates will appear in any lists as care is taken to automatically\ avoid a channel being added to the same list more than once." #define HELP_AJOIN_REPLACE "If no nickname is specified, REPLACE will\ replace all of your grouped nicknames' autojoin lists with %s's list.\ If one of your grouped nicknames is specified, only that nickname's\ list will be replaced. If two nicknames are specified, the second one's\ list (destination) will be replaced by the first one's (source)." #define AJOIN_LIST_SYNTAX "\002AJOIN LIST [\037nickname\037]" #define HELP_AJOIN_LIST "Display's the autojoin list for %s. If one of\ your grouped nicknames is specified, the list for that nickname will\ be displayed." #define AJOIN_CLEAR_SYNTAX "\002AJOIN CLEAR [\037nickname\037]" #define HELP_AJOIN_CLEAR "If no nickname is specified, CLEAR will remove\ all entries from %s's autojoin list. If you specify one of your grouped\ nicknames, all entries in that nickname's autojoin list will be removed." #define MAIN_HELP_SET_SUMMARY_AJOIN "AJOIN Toggle your autojoin list" #define SET_AJOIN_SYNTAX "\002SET AJOIN { ON | OFF | SILENT }" #define HELP_SET_AJOIN "Toggles whether or not your autojoin list is\ performed when you IDENTIFY or UPDATE. The default is ON. The behavior\ of ON is such that you will receive a notification that you're being\ autojoined when it's being performed. If you would rather not receive\ notification, you may SET AJOIN SILENT." #define NOTICE_ADDITIONAL "Command provided by %s module:" #define NOTICE_JOINING "Joining the channels in your ajoin list: " #define NOTICE_ADD "%s has been added to %s's AJOIN list." #define NOTICE_ALREADY "%s was already in your AJOIN list." #define NOTICE_KEYCHANGE "The key for %s has been updated in your AJOIN lists." #define NOTICE_NOMORE "The AJOIN list for %s has reached the\ maximum number of entries (%u), and may hold no more." #define NOTICE_TOO_MANY "Your AJOIN list has too many entries. Autojoin only\ performs upon %u channels." #define NOTICE_DEL "%s has been removed from your AJOIN list." #define NOTICE_GDEL "%s has been removed from any of your grouped nicknames'\ AJOIN lists." #define NOTICE_NOT_IN_LIST "%s is not in your AJOIN list." #define NOTICE_MERGE "%s's AJOIN list has been merged." #define NOTICE_COPY "AJOIN entries from %s have been copied to %s." #define NOTICE_REPLACE "%s's AJOIN list has been replaced by %s's." #define NOTICE_CLEAR "The AJOIN list for %s is now empty." #define NOTICE_LIST "Your AJOIN channels are: " #define NOTICE_COUNT "%u channels are in %s's AJOIN list." #define NOTICE_EMPTY_LIST "The AJOIN list for %s is empty." #define NOTICE_AJOIN_IS_ON "Your autojoin list was already on." #define NOTICE_AJOIN_NOW_ON "Your autojoin list is now on." #define NOTICE_AJOIN_IS_OFF "Your autojoin list was already off." #define NOTICE_AJOIN_NOW_OFF "Your autojoin list is now off." #define NOTICE_AJOIN_IS_SIL "Your autojoin list was already in silent mode." #define NOTICE_AJOIN_NOW_SIL "Your autojoin list is now on and is in silent mode." #define NOTICE_NICK_NOT_GROUP "%s is not one of your grouped nicknames." /*********************************/ /** END OF LANGUAGE DEFINITION ***/ /*********************************/ /************************************************************************** ** DO NOT CHANGE BELOW THIS LINE UNLESS YOU KNOW WHAT ARE YOU DOING !! **************************************************************************/ /*------------------------------------------------------------------------- * -- Database schema -- These are the definitions for the new table * that ns_ajoin uses. It will be added to the database from this * definition if it didn't previously exist, but if you are changing the * schema, you'll need to do the ALTER TABLE queries manually with a MySQL * client and update the rest of the code to use the proper queries, of * course. *-------------------------------------------------------------------------*/ #define SET_AJOIN_DBTAB "mod_ajoin_info" #define SET_AJOIN_SCHEMA "CREATE TABLE " SET_AJOIN_DBTAB " (\ id int(11) unsigned NOT NULL auto_increment, \ nick varchar(255) NOT NULL default '', \ flags tinyint(3) unsigned NOT NULL default '1', \ PRIMARY KEY (id), \ UNIQUE KEY nick (nick), \ KEY nick_index (nick(10)) \ ) TYPE=myISAM " #define AJOIN_DBTAB "mod_ajoin_lists" #define AJOIN_SCHEMA "CREATE TABLE " AJOIN_DBTAB " (\ id int(11) unsigned NOT NULL auto_increment, \ ajoin_info_id int(11) unsigned NOT NULL default '0', \ channel varchar(255) NOT NULL default '', \ chankey varchar(255) NOT NULL default '', \ PRIMARY KEY (id), \ KEY idx_ajoin_info_id (ajoin_info_id) \ ) TYPE=myISAM " /* Defining __GNU_SOURCE is required for the use of asprintf(), which I * use for forming my SQL queries. If this is undefined for portability, * the module still works, but there's less checking for buffer overflow. */ #ifndef _WIN32 #define __GNU_SOURCE #endif #include "module.h" #define AJOIN_ON (1 << 0) #define AJOIN_SILENT (1 << 1) typedef struct ajoinchan_ AjoinChanList; struct ajoinchan_ { AjoinChanList *next; uint32 db_id; /* Unique identifier for the channel entry in the db table */ uint32 info_id; /* To which info entry this channel belongs */ char *channel; /* Channel name */ char *key; /* CHannel key (for +k channels) */ }; typedef struct ajoininfo_ AjoinInfo; struct ajoininfo_ { uint32 db_id; /* Unique identifier for a user's ajoin info in the db table */ char *nick; /* Nickname of user owning this info */ /* Toggle flags are as follows: * User wants to be autojoined: {bit 0} * User wants autojoin to be silent if it's on: {bit 1} */ unsigned char flags; AjoinChanList *channels; /* Beginning channel of the user's channel list */ }; static MYSQL *mysql; /* Utility functions */ void free_chan_list(AjoinChanList *chan); void free_ajoin_info(AjoinInfo *info); void free_ajoin_group(AjoinInfo **group_infos); char *channel_add_numsign(char *channel); char **get_group_nicks(User *u); int check_nick_group(User *u, char *checknick); int get_src_dests(const char *cmdname, AjoinInfo **source, AjoinInfo ***destinations, User *u, char *args); int get_chan_and_key(const char *cmdname, char **channel, char **key, User *u, char *args); void notice_user_ww(char *source, User *u, const char *fmt, ... ); void clean_channelname(char *cn); char *make_sql_list(char **strings); /* Database related routines */ char *quote(char *arg); int mydb_init(void); int do_query(const char *fmt, ... ); AjoinInfo *get_ajoin_info(char *nick); AjoinChanList *get_ajoin_list(AjoinInfo *info); AjoinChanList *db_get_chan(char *nick, char *channel); void db_add_chan(uint32 info_id, char *channel, char *key); void db_del_chan(AjoinChanList *chan); uint32 db_del_group_chan(User *u, char *channel); AjoinInfo **get_group_ajoin_lists(User *u); int db_add_ajoin_info(char *nick); int db_update_ajoin_info(uint32 db_id, unsigned char flags); int update_ajoin_list(AjoinInfo *newinfo); int purgedb(int argc, char *argv[]); void db_change_group_keys(User *u, char *channel, char *newkey); char **group_info_ids(User *u); void db_clear_list(char *nick); /* User command routines */ int ns_ajoin_debug(User *u, char *args); int ns_ajoin_add(User *u, char *args); int ns_ajoin_del(User *u, char *args); int ns_ajoin_gadd(User *u, char *args); int ns_ajoin_gdel(User *u, char *args); int ns_ajoin_merge(User *u, char *args); int ns_ajoin_copy(User *u, char *args); int ns_ajoin_replace(User *u, char *args); int ns_ajoin_list(User *u, char *args); int ns_ajoin_clear(User *u, char *args); int ns_ajoin(User *u); int ns_set(User *u); int do_on_identify(User *u); /* Help display routines */ int null_func(User *u); void main_ns_help(User *u); int ns_help_ajoin(User *u); int ns_help_ajoin_add(User *u); int ns_help_ajoin_del(User *u); int ns_help_ajoin_gadd(User *u); int ns_help_ajoin_gdel(User *u); int ns_help_ajoin_merge(User *u); int ns_help_ajoin_copy(User *u); int ns_help_ajoin_replace(User *u); int ns_help_ajoin_list(User *u); int ns_help_ajoin_clear(User *u); int main_ns_help_set(User *u); int ns_help_set_ajoin(User *u); /*************************************************************************** * AnopeInit(): Function that's called by Anope at initial module load time */ int AnopeInit(int argc, char **argv) { Command *c; #ifndef USE_MYSQL alog("[%s] MYSQL is not enabled. This module requires MYSQL !", MODNAME); alog("[%s] Module not loaded.", MODNAME); return MOD_STOP; #endif if (!do_mysql) { alog( "[%s] MYSQL is not configured. Please edit your " "services.conf.", MODNAME ); alog("[%s] Module not loaded.", MODNAME); return MOD_STOP; } #if !(defined(IRC_UNREAL) || defined(IRC_UNREAL32) || defined(IRC_PTLINK) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA) || defined(IRC_SHADOWIRCD)) alog("[%s] This module requires the IRCd to be SVSJOIN-capable", MODNAME); alog("[%s] Module not loaded.", MODNAME); return MOD_STOP; #endif /* activate mysql connection and stop if failed */ if (mydb_init()) return MOD_STOP; moduleSetNickHelp(main_ns_help); c = createCommand("AJOIN", ns_ajoin, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_UNIQUE); moduleAddHelp(c, ns_help_ajoin); /* Add help functions for AJOIN */ /* The following moduleAddCommand() / moduleAddHelp() methods * add a null command to the command table for each sub-command. * It's null because the main command function will handle all of * the actual functionality of the command. Doing this gives us * a way to add a help function for each sub-command */ c = createCommand("AJOIN ADD", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_add); c = createCommand("AJOIN DEL", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_del); c = createCommand("AJOIN GADD", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_gadd); c = createCommand("AJOIN GDEL", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_gdel); c = createCommand("AJOIN MERGE", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_merge); c = createCommand("AJOIN COPY", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_copy); c = createCommand("AJOIN REPLACE", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_replace); c = createCommand("AJOIN LIST", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_list); c = createCommand("AJOIN CLEAR", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_ajoin_clear); /* End of AJOIN help functions */ /* We tack our ns_set() onto the beginning of the /nickserv SET * command. This allows our module to check for syntax pertaining * to the AJOIN functionality and act upon it without NickServ's * SET function complaining to the user that "SET AJOIN" is an invalid * command. If the syntax doesn't pertain to us, we can return MOD_CONT * and functionality passes through to NickServ's set command function. * If the syntax does pertain to AJOIN, we act upon it and return MOD_STOP, * which prevents processing of the command to continue onto NickServ's * core SET command. */ c = createCommand("SET", ns_set, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_HEAD); /* We tack our actual ajoin function to the end of NickServ's core * identify/update commands. This allows Nickserv to do all of the * stuff that it needs to do when a user identifies, and when control * gets to us, we can check their ajoin list, and use SVSJOIN to * put them in the channels that they have in their list. */ c = createCommand("IDENTIFY", do_on_identify, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); c = createCommand("ID", do_on_identify, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); c = createCommand("UPDATE", do_on_identify, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); /* Doing the following moduleAddCommand() / moduleAddHelp() method * allows us to tack help information onto the end of the main * /nickserv HELP SET help command. */ c = createCommand("SET", null_func, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, main_ns_help_set); /* Add a /nickserv 'HELP SET AJOIN" command. */ c = createCommand("SET AJOIN", NULL, NULL, -1, -1, -1, -1, -1); moduleAddCommand(NICKSERV, c, MOD_TAIL); moduleAddHelp(c, ns_help_set_ajoin); purgedb(0, NULL); db_keepalive(0, NULL); moduleAddAuthor(AUTHOR); moduleAddVersion(VERSION); return MOD_CONT; } /* End of AnopeInit() */ /************************************************************************** * AnopeFini(): This function is called by Anope at module unload time. * Any final clean-up routines should be put in here */ void AnopeFini(void) { mysql_close(mysql); moduleDelCallback("NSAJOINPURGEDB"); moduleDelCallback("NSAJOINKEEPALIVE"); } /************************************************************************** * General utility and memory management functions **************************************************************************/ /*************************************************************************** * free_chan_list(): Takes an AjoinChanList, free()s the memory associated * with its members, then frees the structure itself. */ void free_chan_list(AjoinChanList *chan) { AjoinChanList *nextchan; while (chan != NULL) { free(chan->channel); free(chan->key); nextchan = chan->next; free(chan); chan = nextchan; } } /* End free_chan_list() */ /*************************************************************************** * free_ajoin_info(): free()s all of the dynamically-allocated memory in * use by an AjoinInfo structure (pointed to by info) and its attached * AjoinChanList structures. When that's done, it free()s the structure * pointed to by info. */ void free_ajoin_info(AjoinInfo *info) { free_chan_list(info->channels); free(info->nick); free(info); } /* End free_ajoin_info() */ /*************************************************************************** * free_ajoin_group(): Takes a null-terminated array of AjoinInfo's and * frees all of their contents, then frees the array itself. */ void free_ajoin_group(AjoinInfo **group_infos) { AjoinInfo **marker = group_infos; for ( ; *marker != NULL; marker++ ) free_ajoin_info(*marker); free(group_infos); } /* End free_ajoin_group() */ /*************************************************************************** * channel_add_numsign(): Looks at a string. If the first character is not * a number sign (#) or an ampersand (&), a new string is created with the * number sign (#) prepended to it. Otherwise, a pointer to the same * string is returned. * * WARNING: This function calls free() on the string which is passed to it * if it creates a new string. Be sure that "channel" is free()-able. */ char *channel_add_numsign(char *channel) { char *tempstr; if ( ( *channel != '#' ) && ( *channel != '&' ) ) { tempstr = smalloc(strlen(channel)+2); tempstr[0] = '#'; tempstr[1] = '\0'; strcat(tempstr, channel); free(channel); return tempstr; } else return channel; } /* End channel_add_numsign() */ /*************************************************************************** * get_group_nicks(): Creates an array of pointers that point to each * of the nickname strings in a user's nick group and returns the array * This function uses calloc() to create the space for the array. Don't * forget to free() it when you're done with it. (Just free the array, * not the individual strings.) * * This was about as optimized for processing speed as I could possibly * have made it without doing it in assembler, I think. At first glance, * it may seem obfuscated, mostly because it makes use of pointer * arithmetic instead of a ton of dereferencing by member names and indexes. * The bulk of the needed dereferencing gets done in the variable * declaration section. * * The for () loop just increments array pointers at each iteration to * get the next array element, then copies the appropriate pointer * from the aliaslist array to the one in the the nicks array. * * WARNING: There is no checking done to see if the user is registered * and actually has a NickAlias or NickCore before processing. These * checks must be done before calling this function. */ char **get_group_nicks(User *u) { void **aliaslist = u->na->nc->aliases.list; uint32 num_aliases = u->na->nc->aliases.count; char **nicks = scalloc(num_aliases + 1, sizeof(char *)); char **nick = nicks; for ( ; (nick - nicks) < num_aliases; nick++, aliaslist++) *nick = ((NickAlias *) *aliaslist)->nick; *nick = NULL; return nicks; } /* End get_group_nicks() */ /*************************************************************************** * check_nick_group(): Return 1 if checknick is in u's group. If checknick * is not in u's group, inform the user that it's not one of their * nicknames, free() checknick, and return 0. free() is performed because * this function was written specifically for reducing duplication of effort * before returning to a calling process. */ int check_nick_group(User *u, char *checknick) { char **nicks, **marker, *nick; nicks = get_group_nicks(u); for ( marker = nicks; *marker != NULL; marker++ ) if (stricmp(*marker, checknick) == 0) break; nick = *marker; free(nicks); if ( !(nick) ) { notice_user_ww(s_NickServ, u, NOTICE_NICK_NOT_GROUP, checknick); free(checknick); return 0; } return 1; } /* End check_nick_group() */ /*************************************************************************** * get_src_dests(): Populates source and destinations with AjoinInfo * structures for a source and destination nickname, specified by args. * Returns 1 if there were no erros, 0 if there were. */ int get_src_dests(const char *cmdname, AjoinInfo **source, AjoinInfo ***destinations, User *u, char *args) { AjoinInfo **destination; char *source_arg = NULL, *dest_arg = NULL; if (args) { /* Check for too many arguments */ source_arg = myStrGetToken(args, ' ', 2); if (source_arg) { free(source_arg); notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_CP_REPL_SYNTAX, cmdname); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, cmdname); return 0; } /* Get the source and destination sub-strings from args */ source_arg = myStrGetToken(args, ' ', 0); dest_arg = myStrGetToken(args, ' ', 1); /* If there wasn't a second argument, the first argument is considered * to be the destination and the source is the current nickname */ if (!dest_arg) { dest_arg = source_arg; source_arg = sstrdup(u->nick); } /* Make sure that source_arg and dest_arg * are nicknames in the user's group */ if (!check_nick_group(u, dest_arg)) { free(source_arg); /* check_nick_group() takes care of free()ing dest_arg */ return 0; } if (!check_nick_group(u, source_arg)) { free(dest_arg); /* check_nick_group() takes care of free()ing source_arg */ return 0; } /* Since we're only dealing with one destination, create a two element * array for destinations, and populate the first element with the * AjoinInfo structure and channels for the destination. Populate the * second element with NULL to mark the end of the array */ *destinations = scalloc(2, sizeof(AjoinInfo *)); **destinations = get_ajoin_info(dest_arg); free(dest_arg); (**destinations)->channels = get_ajoin_list(**destinations); *destinations[1] = NULL; } else { /* If no arguments were given, the source is the current user's nickname * and the destination is all of the nicknames in the user's group */ source_arg = sstrdup(u->nick); *destinations = get_group_ajoin_lists(u); } /* Now that we know the source nickname, populate the source * AjoinInfo structure with data for that nickname */ *source = get_ajoin_info(source_arg); free(source_arg); (*source)->channels = get_ajoin_list(*source); return 1; } /*************************************************************************** * get_chan_and_key(): Reads arguments for a channel/key pair from the * user, does all of the necessary sanity checks and error messages, then * returns 1 for successful population or 0 for unsuccessful */ int get_chan_and_key(const char *cmdname, char **channel, char **key, User *u, char *args) { /* Check for too many arguments - uses "channel" temporarily to * store the third string token in args */ if (args) *channel = myStrGetToken(args, ' ', 2); /* If there are too many or no arguments, display usage * information to the user and return */ if (*channel || !args) { if (*channel) free(*channel); notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_ADD_SYNTAX, cmdname); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, cmdname); return 0; } /* Get the channel and string information * from the arguments given by the user */ *channel = channel_add_numsign(myStrGetToken(args, ' ', 0)); clean_channelname(*channel); *key = myStrGetToken(args, ' ', 1); if (*key == NULL) *key = sstrdup(""); return 1; } /**************************************************************** * line_lastchar(): Finds the character at the maximum line * length, taking into account non-printable characters and * increasing the max length by the number of non-printable * characters that it finds. This is specifically a sub-function * of notice_user_ww() */ char *line_lastchar(char *s) { char *ptr; uint32 num_nonprint = 0; ptr = s; if (ptr) while ( (*ptr) && ((ptr - s) < (LINELENGTHMAX + num_nonprint)) ) { switch (*ptr) { /* Still need to find the character for reverse */ case '\002': /* Bold */ case '\037': /* Underline */ ++num_nonprint; break; default: break; } ++ptr; } return ptr; } /* End of line_lastchar() sub-function */ /*************************************************************************** * Uses LINELENGTHMAX definition to determine how many characters are * sent to the user before sending the next line as a new notice. If * a newline character (\n) is encountered in the paragraph, following * text will be sent as a new notice regardless of the length of the line. * (So it's notice_user() with word wrap.) */ void notice_user_ww(char *source, User *u, const char *fmt, ...) { va_list args; #ifdef __GNU_SOURCE char *paragraph = NULL; #else char paragraph[BUFSIZE]; #endif char *paragraphend = NULL, *linestart = NULL, *lineend = NULL, *marker = NULL; if (!fmt) return; /* Do any printf()-style formatting substitutions */ va_start(args, fmt); #ifdef __GNU_SOURCE vasprintf(¶graph, #else *paragraph = '\0'; vsnprintf(paragraph, BUFSIZE, #endif fmt, args); #if 0 ) /* Fixes vim's context highlighting */ #endif va_end(args); if (!paragraph || !(*paragraph)) return; /* Find and replace newlines ('\n') with null characters ('\0') */ for (paragraphend = paragraph; *paragraphend; paragraphend++) if ( *paragraphend == '\n' ) *paragraphend = '\0'; /* Back paragraphend up one so that it's pointing to the * last character instead of the null termination */ --paragraphend; linestart = paragraph; marker = paragraph; /* As long as we haven't reached the end of the paragraph ... */ while ( marker < paragraphend ) { /* Set the lineend pointer to the character at the maximum line length */ lineend = line_lastchar(marker); /* If we haven't reached the end of the line */ if (*lineend) { marker = lineend; /* Back the marker up until it finds a space * or it hits the beginning of the line */ while ( (*marker != ' ') && (marker != linestart) ) --marker; /* If it hit the beginning of the line, put it back * to the maximum line length, and increment until * we find the end of the line or a space */ if ( marker == linestart ) { marker = lineend; while ( (*marker) && (*marker != ' ') ) ++marker; } lineend = marker; *lineend = '\0'; } else /* (If we were at the end of the line, set the marker to the end) */ marker = lineend; /* Display the line to the user. If the first character is null, * display a line with a single space. */ notice_user(source, u, *linestart ? linestart : " "); /* At this point, marker is pointing to the null character at the * end of the line that we just processed. Incrementing the pointer * by one will put us at the first character in the next line of the * paragraph. The linestart pointer is also set to that location * because we want it to know where the beginning of the next line is. */ ++marker; linestart = marker; } } /*************************************************************************** * make_sql_list(): Takes an array of strings and returns a newly-allocated * copy of the string information in the format: * "'string1', 'string2', 'string3'", used for listing values in SQL queries. * The strings argument must have a pointer to NULL as its last element. * All of the strings returned within the single quotes have been passed * through quote() so that they are sql-safe. */ char *make_sql_list(char **strings) { char *sql_list_str = NULL, *marker = NULL; char **escaped_strings = NULL; uint32 num_strings = 0, allstrings_len = 0; uint32 *string_lens = NULL; /* Count the number of strings in the array */ for (; *strings != NULL; strings++) ++num_strings; strings -= num_strings; /* Create an array of integers to correspond with the lengths of * each of the strings in the strings array. */ string_lens = scalloc(num_strings, sizeof(uint32 *)); /* Create another string array to hold the results from quote() */ escaped_strings = scalloc(num_strings + 1, sizeof(char *)); /* Iterate through each string in the array, generating the array of * quote()d strings and the array of lengths of each of those strings. */ for ( ; *strings != NULL; strings++, string_lens++, escaped_strings++ ) { *escaped_strings = quote(*strings); *string_lens = strlen(*escaped_strings); allstrings_len += *string_lens; } *escaped_strings = NULL; /* Set the last one to null */ /* "Rewind" the pointers */ escaped_strings -= num_strings; strings -= num_strings; string_lens -= num_strings; /* Create a string large enough to hold the resulting sql list */ sql_list_str = smalloc((num_strings * (allstrings_len + 4)) + 1); /* Loop through each of the new, sql-quoted strings, append them to * the final location, and mark them up with the single quotes, commas, * and spaces. */ marker = sql_list_str; for ( ; *escaped_strings != NULL; escaped_strings++, string_lens++ ) { *marker++ = '\''; memcpy(marker, *escaped_strings, *string_lens); marker += *string_lens; *marker++ = '\''; if ( *(escaped_strings + 1) != NULL ) { *marker++ = ','; *marker++ = ' '; } } *marker = '\0'; /* Rewind the pointers again */ escaped_strings -= num_strings; string_lens -= num_strings; /* Clean up after ourselves. Because we obtained each of our escaped * strings by way of quote() and because quote uses malloc(), we free() * each string first, before free()ing the array */ for (; *escaped_strings != NULL; escaped_strings++) free(*escaped_strings); escaped_strings -= num_strings; free(escaped_strings); free(string_lens); return sql_list_str; } /* End make_sql_list() */ /*************************************************************************** * clean_channelname(): Checks the name of a channel for control characters, * spaces, non-breaking spaces, and commas. If it finds any, it truncates * the channel string where the first one is found and returns. * * (Borrowed from the UnrealIRCd channel.c) */ void clean_channelname(char *cn) { u_char *ch = (u_char *)cn; for (; *ch; ch++) /* Don't allow any control chars, the space, the comma, * or the "non-breaking space" in channel names. * Might later be changed to a system where the list of * allowed/non-allowed chars for channels was a define * or some such. * --Wizzu */ if (*ch < 33 || *ch == ',' || *ch == 160) { *ch = '\0'; return; } } /*************************************************************************** * Functions which display help text to the user * * These are mainly self-explanatory by the function name, so there isn't * a comment explaining each one. ***************************************************************************/ int null_func(User *u) { return MOD_CONT; } void main_ns_help(User *u) { notice_user_ww(s_NickServ, u, " " MAIN_HELP_SUMMARY_AJOIN ); } int ns_help_ajoin(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_ADD_SYNTAX, "ADD"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_DEL_SYNTAX); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_ADD_SYNTAX, "GADD"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_MERGE_SYNTAX); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_CP_REPL_SYNTAX, "COPY"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_CP_REPL_SYNTAX, "REPLACE"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_LIST_SYNTAX); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_CLEAR_SYNTAX); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN, MAX_AJOIN_CHANNELS); return MOD_CONT; } int ns_help_ajoin_add(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_ADD_SYNTAX, "ADD"); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_ADD, u->nick); return MOD_CONT; } int ns_help_ajoin_del(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_DEL_SYNTAX); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_DEL, u->nick); return MOD_CONT; } int ns_help_ajoin_gadd(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_ADD_SYNTAX, "GADD"); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_GADD); return MOD_CONT; } int ns_help_ajoin_gdel(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_GDEL_SYNTAX); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_GDEL); return MOD_CONT; } int ns_help_ajoin_merge(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_MERGE_SYNTAX); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_MERGE, MAX_AJOIN_CHANNELS); return MOD_CONT; } int ns_help_ajoin_copy(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_CP_REPL_SYNTAX, "COPY"); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_COPY, u->nick); return MOD_CONT; } int ns_help_ajoin_replace(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_CP_REPL_SYNTAX, "REPLACE"); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_REPLACE, u->nick); return MOD_CONT; } int ns_help_ajoin_list(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_LIST_SYNTAX); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_LIST, u->nick); return MOD_CONT; } int ns_help_ajoin_clear(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_CLEAR_SYNTAX); notice_user_ww(s_NickServ, u, " \n" HELP_AJOIN_CLEAR, u->nick); return MOD_CONT; } int main_ns_help_set(User *u) { notice_user_ww(s_NickServ, u, NOTICE_ADDITIONAL, MODNAME); notice_user_ww(s_NickServ, u, " " MAIN_HELP_SET_SUMMARY_AJOIN ); return MOD_CONT; } int ns_help_set_ajoin(User *u) { notice_user_ww(s_NickServ, u, SYNTAX_WORD SET_AJOIN_SYNTAX); notice_user_ww(s_NickServ, u, " \n" HELP_SET_AJOIN); return MOD_CONT; } /************************************************************************** * Routines called directly by Anope on command events **************************************************************************/ /******************************************************** * ns_set_sub(): Sub-fucntion which handles the actual * user-requested changes. Returns 0 if it didn't change * anything or 1 if it did. */ int ns_set_sub(User *u, char *option, AjoinInfo *info) { if (stricmp(option, "ON") == 0) { if ( (info->flags & AJOIN_ON) && !(info->flags & AJOIN_SILENT) ) { notice_user_ww(s_NickServ, u, NOTICE_AJOIN_IS_ON); return 1; } /* Turn off silent bit */ info->flags &= ~AJOIN_SILENT; /* Turn on use autojoin bit */ info->flags |= AJOIN_ON; notice_user_ww(s_NickServ, u, NOTICE_AJOIN_NOW_ON); return 1; } if (stricmp(option, "OFF") == 0) { if ( !(info->flags & AJOIN_ON) ) { notice_user_ww(s_NickServ, u, NOTICE_AJOIN_IS_OFF); return 1; } /* Turn off use autojoin and silent bits */ info->flags &= ~AJOIN_ON; info->flags &= ~AJOIN_SILENT; notice_user_ww(s_NickServ, u, NOTICE_AJOIN_NOW_OFF); return 1; } if ( stricmp(option, "SILENT") == 0 ) { if ( info->flags & AJOIN_SILENT ) { notice_user_ww(s_NickServ, u, NOTICE_AJOIN_IS_SIL); return 1; } /* Turn on use autojoin and silent bits */ info->flags |= AJOIN_ON; info->flags |= AJOIN_SILENT; notice_user_ww(s_NickServ, u, NOTICE_AJOIN_NOW_SIL); return 1; } return 0; } /* End ns_set_sub() */ /************************************************************************** * ns_set(): When a user issues a /nickserv set, this function is called * ahead of Anope's core SET function. It checks the command buffer to * see if the user wanted to set an AJOIN option. If so, the appropriate * measures are taken to set the flags that they're requesting or give * syntax information if they fat finger the command. If the user wasn't * looking to do anything with AJOIN, flow continues to the Anope's core * set command. */ int ns_set(User *u) { char *args = NULL, *option = NULL; AjoinInfo *info = NULL; int retval = 0; /* If there were no arguments specified, return and allow the * core SET command fucntion handle the error message for that */ args = moduleGetLastBuffer(); if (!args) return MOD_CONT; /* Get the first argument. If it's not AJOIN, it doesn't pertain to us, * so return and allow the core SET command function handle the command */ option = myStrGetToken(args, ' ', 0); if ( (!option) || (stricmp(option, "AJOIN") != 0) ) { if (option) free(option); return MOD_CONT; } /* Reset the option string */ free(option); option = NULL; /* Nothing that we do is any good if the nickname isn't * registered and identified. If it isn't, return and * allow the core SET command to handle the error. */ if (!nick_identified(u)) return MOD_CONT; /* Get the actual SET AJOIN option from the user. If there * was none or if there was no action taken by ns_set_sub() * (meaning that the user didn't specify an option that we * recognize), display an error to the user */ option = myStrGetToken(args, ' ', 1); info = get_ajoin_info(u->nick); if ( !(option) || !(retval = ns_set_sub(u, option, info)) ) { notice_user_ww(s_NickServ, u, SYNTAX_WORD SET_AJOIN_SYNTAX); notice_user_ww(s_NickServ, u, HELP_USAGE_HEAD "SET AJOIN" HELP_USAGE_TAIL, s_NickServ); } /* Clean up after ourselves, update the database with changes made * to the AjoinInfo structure for the user, and halt processing on * the /nickserv SET command by returning MOD_STOP */ if (option) free(option); if (info) { if (retval) db_update_ajoin_info(info->db_id, info->flags); free_ajoin_info(info); } return MOD_STOP; } /* End of ns_set() */ /*************************************************************************** * ns_ajoin_debug(): Utility command, used for testing things. * "Undocumented" :) */ int ns_ajoin_debug(User *u, char *args) { if (stricmp(args, "GROUP") == 0) /* essentially a glist command */ { AjoinInfo **group_infos = get_group_ajoin_lists(u); AjoinInfo **infomarker; AjoinChanList *currentchan; for (infomarker = group_infos; *infomarker != NULL; infomarker++) for (currentchan = (*infomarker)->channels; currentchan != NULL; currentchan = currentchan->next) notice_user_ww(s_NickServ, u, "%s: %s", (*infomarker)->nick, currentchan->channel ); free_ajoin_group(group_infos); return MOD_STOP; } if ( (stricmp(args, "PURGEDB") == 0) && is_services_admin(u) ) { moduleDelCallback("NSAJOINPURGEDB"); purgedb(0, NULL); notice_user_ww(s_NickServ, u, "Database cleaned"); return MOD_STOP; } return MOD_STOP; } /* End ns_ajoin_debug() */ /*************************************************************************** * ns_ajoin_add(): Allows a user to add a channel/key pair to their ajoin * list. */ int ns_ajoin_add(User *u, char *args) { AjoinInfo *info; AjoinChanList *chan = NULL; char *channel = NULL, *key = NULL; if ( !(get_chan_and_key("ADD", &channel, &key, u, args)) ) return MOD_STOP; chan = db_get_chan(u->nick, channel); if (!chan) { info = get_ajoin_info(u->nick); db_add_chan(info->db_id, channel, key); db_change_group_keys(u, channel, key); notice_user_ww(s_NickServ, u, NOTICE_ADD, channel, u->nick); free_ajoin_info(info); } else { if (strcmp(chan->key, key) != 0) { db_change_group_keys(u, channel, key); notice_user_ww(s_NickServ, u, NOTICE_KEYCHANGE, chan->channel); } else notice_user_ww(s_NickServ, u, NOTICE_ALREADY, channel); free_chan_list(chan); } free(channel); free(key); return MOD_STOP; } /* End ns_ajoin_add() */ /*************************************************************************** * ns_ajoin_del(): Allows a user to remove a channel/key pair from their * ajoin list. */ int ns_ajoin_del(User *u, char *args) { AjoinChanList *chan; /* Check for too few or too many arguments */ if ( !(args) || (strchr(args, ' ')) ) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_DEL_SYNTAX); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, "DEL"); return MOD_STOP; } chan = db_get_chan(u->nick, args); if (chan != NULL) { db_del_chan(chan); notice_user_ww(s_NickServ, u, NOTICE_DEL, chan->channel); free(chan); return MOD_STOP; } notice_user_ww(s_NickServ, u, NOTICE_NOT_IN_LIST, args); return MOD_STOP; } /* End ns_ajoin_del() */ /*************************************************************************** * ns_ajoin_gadd(): Allows a user to add a channel/key pair to all of * their grouped nicknames. */ int ns_ajoin_gadd(User *u, char *args) { char *channel = NULL, *key = NULL; AjoinChanList *currentchan; AjoinInfo **group_infos, **currentinfo; if ( !(get_chan_and_key("GADD", &channel, &key, u, args)) ) return MOD_STOP; /* Loop through each nickname in the user's group, adding the new * channel to lists that didn't previously have the channel */ group_infos = get_group_ajoin_lists(u); for (currentinfo = group_infos; *currentinfo != NULL; currentinfo++) { currentchan = (*currentinfo)->channels; for ( ; currentchan != NULL; currentchan = currentchan->next ) if (stricmp(currentchan->channel, channel) == 0) break; if ( currentchan == NULL ) { db_add_chan((*currentinfo)->db_id, channel, key); notice_user_ww(s_NickServ, u, NOTICE_ADD, channel, (*currentinfo)->nick); } } /* Since I wrote a function to handle this task in a single, simple * database query, update the keys for this channel in every list * in the group to include the key specified on the command line. * This serves the purpose of updating the keys for the channels that * we left alone in the preceeding code. */ db_change_group_keys(u, channel, key); /* Clean up and return */ free(channel); free(key); free_ajoin_group(group_infos); return MOD_STOP; } /* End ns_ajoin_gadd() */ /*************************************************************************** * ns_ajoin_gdel(): Group delete. Removes a channel from any grouped * nickname's list which contains it. */ int ns_ajoin_gdel(User *u, char *args) { if (args != NULL) if ( strchr(args, ' ') == NULL ) { if (db_del_group_chan(u, args) == 0) notice_user_ww(s_NickServ, u, NOTICE_NOT_IN_LIST, args); else notice_user_ww(s_NickServ, u, NOTICE_GDEL, args); return MOD_STOP; } notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_GDEL_SYNTAX); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, "GDEL"); return MOD_STOP; } /* End ns_ajoin_gdel() */ /*************************************************************************** * ns_ajoin_merge(): Allows a user to basically bitwise AND their channel * list together. In other words, take all of the channels in the lists * from all of their grouped nicknames, put 'em together, remove the * duplicates (only add one), and replace all of the grouped nicknames' * lists with the new, cumulative list. */ int ns_ajoin_merge(User *u, char *args) { AjoinChanList *merged_chans = NULL; AjoinChanList *currentchan, *alreadychan, *marker, seedchan; AjoinInfo **group_infos; AjoinInfo **currentinfo, info; /* Do all of the checks needed to be sure that the * arguments specified by the user are valid */ if (args != NULL) { if ( strchr(args, ' ') ) /* Check for too many arguments */ { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_MERGE_SYNTAX); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, "MERGE"); return MOD_STOP; } if (!check_nick_group(u, args)) return MOD_STOP; } seedchan.next = NULL; marker = &seedchan; group_infos = get_group_ajoin_lists(u); /* Create a new AjoinChanList with the channels from all * of the grouped nicknames, leaving out duplicates */ for ( currentinfo = group_infos; *currentinfo != NULL; currentinfo++ ) { currentchan = (*currentinfo)->channels; for ( ; currentchan != NULL; currentchan = currentchan->next ) { alreadychan = seedchan.next; for ( ; alreadychan != NULL; alreadychan = alreadychan->next) if ( stricmp(alreadychan->channel, currentchan->channel) == 0 ) break; if ( alreadychan == NULL ) { marker->next = smalloc(sizeof(AjoinChanList)); marker = marker->next; memcpy(marker, currentchan, sizeof(AjoinChanList)); marker->next = NULL; } } } merged_chans = seedchan.next; if (args != NULL) { /* Find the AjoinInfo associated with the nick specified by the user */ for ( currentinfo = group_infos; *currentinfo != NULL; currentinfo++ ) if ( stricmp((*currentinfo)->nick, args) == 0 ) break; /* Because we already checked for the nickname specified by the * user as being one in the user's group, there shouldn't be a * scenario where currentinfo is null at this point */ memcpy(&info, *currentinfo, sizeof(AjoinInfo)); info.channels = merged_chans; update_ajoin_list(&info); notice_user_ww(s_NickServ, u, NOTICE_MERGE, info.nick); } else { /* Replace all of the grouped nickname's list with the merged list */ for ( currentinfo = group_infos; *currentinfo != NULL; currentinfo++ ) { memcpy(&info, *currentinfo, sizeof(AjoinInfo)); info.channels = merged_chans; update_ajoin_list(&info); notice_user_ww(s_NickServ, u, NOTICE_MERGE, info.nick); } } /* Clean up after ourselves */ free_ajoin_group(group_infos); /* We don't want to use free_chan_list() on this, since the strings * were just free()d by free_ajoin_group() above */ marker = merged_chans; while ( marker != NULL ) { marker = merged_chans->next; free(merged_chans); merged_chans = marker; } return MOD_STOP; } /* End ns_ajoin_merge() */ /*************************************************************************** * ns_ajoin_copy(): Allows a user to copy the contents of one of their * grouped nicknames' ajoin lists into another (not counting ones that * already exist in the destination's list). */ int ns_ajoin_copy(User *u, char *args) { AjoinInfo *source, **destinations, **destination; AjoinChanList *currentchan, *alreadychan; if (!get_src_dests("COPY", &source, &destinations, u, args)) return MOD_STOP; for ( destination = destinations; *destination != NULL; destination++) { currentchan = source->channels; for ( ; currentchan != NULL; currentchan = currentchan->next ) { alreadychan = (*destination)->channels; for ( ; alreadychan != NULL; alreadychan = alreadychan->next ) if ( stricmp(alreadychan->channel, currentchan->channel) == 0 ) break; if ( alreadychan == NULL ) db_add_chan((*destination)->db_id, currentchan->channel, currentchan->key); } notice_user_ww(s_NickServ, u, NOTICE_COPY, source->nick, (*destination)->nick); } free_ajoin_info(source); free_ajoin_group(destinations); return MOD_STOP; } /* End ns_ajoin_copy() */ /*************************************************************************** * ns_ajoin_replace(): Allows a user to completely replace one of their * grouped nicknames' list with that of another grouped nickname. */ int ns_ajoin_replace(User *u, char *args) { AjoinInfo *source, **destinations, **destination; AjoinChanList *oldchanlist; if (!get_src_dests("REPLACE", &source, &destinations, u, args)) return MOD_STOP; for ( destination = destinations; *destination; destination++ ) { free_chan_list((*destination)->channels); (*destination)->channels = source->channels; update_ajoin_list(*destination); (*destination)->channels = NULL; notice_user_ww(s_NickServ, u, NOTICE_REPLACE, source->nick, (*destination)->nick); } free_ajoin_info(source); free_ajoin_group(destinations); return MOD_STOP; } /* End ns_ajoin_replace() */ /*************************************************************************** * ns_ajoin_list(): Displays an ajoin list to the user. It can be either * the calling user's list or one of their grouped nicknames. */ int ns_ajoin_list(User *u, char *args) { AjoinChanList *currentchan; AjoinInfo *info; unsigned int count = 0; /* If the user sends us an argument after "LIST", we try to find the * nickname in their group for which they want to list channels. */ if (args) { /* If more than one argument was given, * display syntax information and return */ if (strchr(args, ' ') != NULL) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_LIST_SYNTAX); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, "LIST"); return MOD_STOP; } if (!(check_nick_group(u, args))) return MOD_STOP; } else /* If no arguments were passed, set args to the current nickname */ args = u->nick; /* args is now a string which holds a registered nickname in the user's * group for which we will get current ajoin info and channel list */ info = get_ajoin_info(args); info->channels = get_ajoin_list(info); /* If the channel list is empty, tell the user, do clean-up, and return */ if (currentchan = info->channels) { /* Display the list of channels and a channel count to the user */ for ( ; currentchan != NULL; currentchan = currentchan->next, count++) notice_user_ww(s_NickServ, u, "%s", currentchan->channel); notice_user_ww(s_NickServ, u, NOTICE_COUNT, count, info->nick); } else notice_user_ww(s_NickServ, u, NOTICE_EMPTY_LIST, info->nick); /* Clean up after ourselves and return */ free_ajoin_info(info); return MOD_STOP; } /* End ns_ajoin_list() */ /*************************************************************************** * ns_ajoin_clear(): Wipes the entire contents of the calling user's ajoin * list or the list of one of their specified grouped nicknames. */ int ns_ajoin_clear(User *u, char *args) { char *nick; if (args != NULL) { if (strchr(args, ' ')) { notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_CLEAR_SYNTAX); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, "CLEAR"); return MOD_STOP; } if (!(check_nick_group(u, args))) return MOD_STOP; nick = args; } else nick = u->nick; db_clear_list(nick); notice_user_ww(s_NickServ, u, NOTICE_CLEAR, nick); return MOD_STOP; } /* End ns_ajoin_clear() */ /*************************************************************************** * ns_ajoin_sub(): Sub-command processor for ns_ajoin(). */ int ns_ajoin_sub(User *u, char *cmd, char *subcmd) { if (cmd) { if ( stricmp(cmd, "DEBUG") == 0 ) return ns_ajoin_debug(u, subcmd); if ( stricmp(cmd, "ADD") == 0 ) return ns_ajoin_add(u, subcmd); if ( stricmp(cmd, "DEL") == 0 ) return ns_ajoin_del(u, subcmd); if ( stricmp(cmd, "GADD") == 0 ) return ns_ajoin_gadd(u, subcmd); if ( stricmp(cmd, "GDEL") == 0 ) return ns_ajoin_gdel(u, subcmd); if ( stricmp(cmd, "MERGE") == 0 ) return ns_ajoin_merge(u, subcmd); if ( stricmp(cmd, "COPY") == 0 ) return ns_ajoin_copy(u, subcmd); if ( stricmp(cmd, "REPLACE") == 0 ) return ns_ajoin_replace(u, subcmd); if ( stricmp(cmd, "LIST") == 0 ) return ns_ajoin_list(u, subcmd); if ( stricmp(cmd, "CLEAR") == 0 ) return ns_ajoin_clear(u, subcmd); } /* If none of the above ajoin commands are specified, display * syntax information to the user */ notice_user_ww(s_NickServ, u, SYNTAX_WORD AJOIN_ADD_SYNTAX, "ADD"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_DEL_SYNTAX); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_ADD_SYNTAX, "GADD"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_MERGE_SYNTAX); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_CP_REPL_SYNTAX, "COPY"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_CP_REPL_SYNTAX, "REPLACE"); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_LIST_SYNTAX); notice_user_ww(s_NickServ, u, SYNTAX_INDENT AJOIN_CLEAR_SYNTAX); notice_user_ww(s_NickServ, u, NOTICE_HELP_USAGE, s_NickServ, ""); return MOD_STOP; } /* End ns_ajoin_sub() */ /*************************************************************************** * ns_ajoin(): This command is "module unique" in that it doesn't tack * onto the beginning or end of any current core commands. It's called * when a user issues a /nickserv AJOIN command and takes care of doing * their requested AJOIN list manipulations. */ int ns_ajoin(User *u) { char *args = moduleGetLastBuffer(); char *cmd = NULL; char *subcmd = NULL; int retval = 0; if ( !(nick_identified(u)) ) { notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ); return MOD_STOP; } cmd = myStrGetToken(args, ' ', 0); subcmd = myStrGetTokenRemainder(args, ' ', 1); retval = ns_ajoin_sub(u, cmd, subcmd); if (cmd) free(cmd); if (subcmd) free(subcmd); return retval; } /* End ns_ajoin */ /*************************************************************************** * do_on_identify(): This function is called when Anope's core "/nickserv * IDENTIFY" command finishes (It's tacked onto the end of the core command). * This is where the actual checking for invite only, checking for a channel * key and autojoining actually happens. */ int do_on_identify(User *u) { char *args = moduleGetLastBuffer(); AjoinInfo *info = NULL; AjoinChanList *chan = NULL; uint16 count = 0; int getsinvite_ = 0; ChannelInfo *ci; /* stop if nick is not identified */ if (!nick_identified(u)) return MOD_CONT; /* get options from commandline */ if (args != NULL) /* temp can be NULL if triggered by "/ns update" */ if (stristr(args, "nojoin")) return MOD_CONT; info = get_ajoin_info(u->nick); info->channels = get_ajoin_list(info); if ( !(info->flags & AJOIN_ON) || info->channels == NULL ) { free_ajoin_info(info); /* release unused memory */ return MOD_CONT; } if ( !(info->flags & AJOIN_SILENT) ) notice_user(s_NickServ, u, NOTICE_JOINING); for (chan = info->channels; chan != NULL; chan = chan->next) { ++count; if ( count > MAX_AJOIN_CHANNELS ) { notice_user_ww(s_NickServ, u, NOTICE_TOO_MANY, MAX_AJOIN_CHANNELS); break; } if ( ci = cs_findchan(chan->channel) ) { if ( (ci->flags & CI_VERBOTEN) || is_on_chan(ci->c, u) ) continue; if (check_access(u, ci, CA_GETKEY) ) if (ci->c) if (ci->c->key) if (strcmp(chan->key, ci->c->key) != 0) db_change_group_keys(u, chan->channel, ci->c->key); if ( check_access(u, ci, CA_INVITE) ) if (ci->c) { if (ci->c->key) if ( strcmp(chan->key, ci->c->key) != 0 ) getsinvite_ = 1; if ( (ci->c->mode & CMODE_i) || getsinvite_ == 1 ) anope_cmd_invite(whosends(ci), chan->channel, u->nick); } } if ( !(info->flags & AJOIN_SILENT) ) /* If silent bit is not on */ notice_user(s_NickServ, u, "%s", chan->channel); send_cmd(s_NickServ, "SVSJOIN %s %s %s", u->nick, chan->channel, chan->key); } free_ajoin_info(info); /* release unused memory */ return MOD_CONT; } /* End of do_on_identify() */ /************************************************************************** * MySQL Input/Output functions **************************************************************************/ /*************************************************************************** * quote(): Creates a string that is safe to use in a database query. * In other words, it uses mysql_real_escape_string() in order to put * backslashes in front of characters that usually mean something else to * SQL ( ', %, \, etc. ). * * This function uses malloc(), so remember to free() the string when * you're done with it. */ char *quote(char *arg) { int slen; char *qbuf; if (!arg) return sstrdup(""); slen = strlen(arg); qbuf = smalloc((1 + (slen * 2)) * sizeof(char)); mysql_real_escape_string(mysql, qbuf, arg, slen); return qbuf; } /* End quote() */ /************************************************************************** * fill_chan_info(): Takes a MySQL row from AJOIN_DBTAB, in the order of: * id, ajoin_info_id, channel, chankey * Creates a new AjoinChanList structure, and populates the data in it with * data from the row, then returns a pointer to the new AjoinChanList * structure */ AjoinChanList *fill_chan_info(MYSQL_ROW myRow) { AjoinChanList *chan = smalloc(sizeof(AjoinChanList)); chan->next = NULL; chan->db_id = (uint32) atoi(*myRow++); chan->info_id = (uint32) atoi(*myRow++); chan->channel = sstrdup(*myRow++); chan->key = sstrdup(*myRow++); return chan; } /* End fill_chan_info() */ /************************************************************************ * fill_ajoin_info(): Processes a single MySQL row from SET_AJOIN_DBTAB, * ordered: id, nick, flags , creates a new AjoinInfo structure, and * populates it with data from the row. A pointer to the new structure * is returned. */ AjoinInfo *fill_ajoin_info(MYSQL_ROW myRow) { AjoinInfo *info = smalloc(sizeof(AjoinInfo)); info->db_id = (uint32) atoi(*myRow++); info->nick = sstrdup(*myRow++); info->flags = (unsigned char) atoi(*myRow++); info->channels = NULL; return info; } /* End fill_ajoin_info() */ /*************************************************************************** * group_info_ids(): Returns a null-terminated array of strings which * contain the ids from SET_AJOIN_DBTAB for each of the user's grouped * nicknames. Each of the strings as well as the array itself need to * be free()d when you're done with them. */ char **group_info_ids(User *u) { MYSQL_RES *myResult; MYSQL_ROW myRow; char **strings, *tempstr; uint32 num_rows; /* Create an array of strings containing all * of the nicknames in the user's group. */ strings = get_group_nicks(u); /* Create one big string, containing a single-quoted, comma-separated * sql-escaped list of the user's grouped nicknames */ tempstr = make_sql_list(strings); /* Individual strings in the array should not be free()d since they're * pointers to where Anope's actual core is storing them */ free(strings); do_query( "SELECT id FROM " SET_AJOIN_DBTAB " WHERE nick IN ( %s )", tempstr ); free(tempstr); myResult = mysql_store_result(mysql); num_rows = (uint32) mysql_num_rows(myResult); if (num_rows > 0) { /* I'm using the strings array to store fast reference to the db_id's * in string form so that I can turn around and use 'em in another * query just after this one. Here, we allocate memory to hold the * same number of strings as there are rows plus a null pointer */ strings = scalloc(num_rows + 1, sizeof(char *)); /* Create and populate AjoinInfo structures for each row */ while (myRow = mysql_fetch_row(myResult)) { *strings = sstrdup(myRow[0]); strings++; } /* Terminate the arrays with NULL pointers */ *strings = NULL; /* "Rewind" the pointers */ strings -= num_rows; } return strings; } /* End group_info_ids() */ /************************************************************************ * get_ajoin_info(): get_ajoin_list() with half the calories. * This function doesn't bother getting the user's list of AJOIN channels * from the database. This can be used if we're only planning on looking * at or updating the info/flags in the SET_AJOIN_DBTAB database table. * This function uses malloc() to create the returned structure, so * don't forget to free() it when you're done with it. */ AjoinInfo *get_ajoin_info(char *nick) { char *qnick = NULL; MYSQL_RES *myResult; MYSQL_ROW myRow; AjoinInfo *info; qnick = quote(nick); do_query( "SELECT id, nick, flags FROM " SET_AJOIN_DBTAB " WHERE nick = '%s'", qnick ); free(qnick); myResult = mysql_store_result(mysql); if (mysql_num_rows(myResult) == 1) { myRow = mysql_fetch_row(myResult); info = fill_ajoin_info(myRow); } else { /* If the user doesn't have any ajoin info in the * database yet, add a new entry with default values * and read it back to get a real db_id */ db_add_ajoin_info(nick); info = get_ajoin_info(nick); /* Lovely use of recursion */ } mysql_free_result(myResult); return info; } /* End of get_ajoin_info() */ /************************************************************************** * get_ajoin_list(): Gets results from get_ajoin_info() then also * queries the database for the user's list of ajoin channels and keys. * This function creates lots of things with malloc(), so remember to * free them all if you're done with them. */ AjoinChanList *get_ajoin_list(AjoinInfo *info) { MYSQL_RES *myResult; MYSQL_ROW myRow; AjoinChanList *currentchan, headchan; headchan.next = NULL; do_query( "SELECT id, ajoin_info_id, channel, chankey FROM " AJOIN_DBTAB " WHERE ajoin_info_id = '%u'", info->db_id ); myResult = mysql_store_result(mysql); currentchan = &headchan; while (myRow = mysql_fetch_row(myResult)) { currentchan->next = fill_chan_info(myRow); currentchan = currentchan->next; } mysql_free_result(myResult); currentchan = headchan.next; return currentchan; } /* End of get_ajoin_list() */ /************************************************************************** * get_group_ajoin_lists(): Returns an array of AjoinInfo pointers which * contain the info and ajoin lists for all of the nicks in a user's group. * The number of array elements is one more than the number of nicknames * in the group. The extra element dereferences to NULL to mark it as the * end of the array. * * We use calloc() and malloc() to create the array and its elements, so * remember to free the elements of the array and the array itself when * you're done with it. free_ajoin_group() does a good job of it. */ AjoinInfo **get_group_ajoin_lists(User *u) { char **nicks; uint32 num_nicks = 0; AjoinInfo **group_infos = NULL; /* Count the number of nicknames in the user's group */ for (nicks = get_group_nicks(u); *nicks; nicks++) ++num_nicks; nicks -= num_nicks; /* Allocate a large enough space to hold the array of AjoinInfos */ group_infos = scalloc(num_nicks + 1, sizeof(AjoinInfo *)); /* Loop through all of the nicknames, get their info * and channels, and add them to the array */ for ( ; *nicks; nicks++, group_infos++) { *group_infos = get_ajoin_info(*nicks); (*group_infos)->channels = get_ajoin_list(*group_infos); } /* Terminate the array with a NULL */ *group_infos = NULL; /* "Rewind" the pointers */ nicks -= num_nicks; group_infos -= num_nicks; free(nicks); return group_infos; } /* End get_group_ajoin_lists() */ /*************************************************************************** * db_add_ajoin_info(): Add a new entry to the SET_AJOIN_DBTAB */ int db_add_ajoin_info(char *nick) { MYSQL_RES *myResult; char *sql_escaped_str = NULL; sql_escaped_str = quote(nick); do_query( "INSERT INTO " SET_AJOIN_DBTAB " ( nick ) VALUES ( '%s' )", sql_escaped_str ); free(sql_escaped_str); myResult = mysql_store_result(mysql); mysql_free_result(myResult); return 0; } /* End db_add_ajoin_info() */ /*************************************************************************** * update_ajoin_info(): Put the user's updated ajoin data into the database. */ int db_update_ajoin_info(uint32 db_id, unsigned char flags) { MYSQL_RES *myResult; do_query( "UPDATE " SET_AJOIN_DBTAB " SET flags=%u " " WHERE id = %u", flags, db_id ); myResult = mysql_store_result(mysql); mysql_free_result(myResult); return 0; } /* End db_update_ajoin_info() */ /*************************************************************************** * update_ajoin_list(): Replaces the database records for a user's ajoin * list with the channel list at info->channels. */ int update_ajoin_list(AjoinInfo *newinfo) { MYSQL_RES *myResult; AjoinInfo *oldinfo; AjoinChanList *currentchanold, *currentchannew; char *tempstr1, *tempstr2; oldinfo = get_ajoin_info(newinfo->nick); oldinfo->channels = get_ajoin_list(oldinfo); /* If neither the old nor the new infos have channels, * we don't need to do anything */ if ( !((oldinfo->channels == NULL) && (newinfo->channels == NULL)) ) { /* First, go through the new list and find channels which exist there * and not in the old one so that they may be added. If matching * channels are found, check their keys. If they keys change, update * the channel in the database with the new key */ currentchannew = newinfo->channels; for ( ; currentchannew != NULL; currentchannew = currentchannew->next ) { currentchanold = oldinfo->channels; for ( ; currentchanold != NULL; currentchanold = currentchanold->next ) if ( stricmp(currentchanold->channel, currentchannew->channel) == 0 ) break; /* If we find the same channel in both lists, check to see if its * key has changed. If it has, update the database with the new key */ if (currentchanold != NULL) { if ( stricmp(currentchanold->key, currentchannew->key) != 0 ) { tempstr1 = quote(currentchannew->key); do_query( "UPDATE " AJOIN_DBTAB " SET chankey='%s' " " WHERE id = %u", tempstr1, currentchanold->db_id ); free(tempstr1); myResult = mysql_store_result(mysql); mysql_free_result(myResult); } } else /* If we don't find the channel in both lists, add it to the db */ { currentchannew->info_id = newinfo->db_id; db_add_chan(newinfo->db_id, currentchannew->channel, currentchannew->key); } } /* Next, go through the old list and find channels that don't exist * in the new list. If any are found, remove them from the database */ currentchanold = oldinfo->channels; for ( ; currentchanold != NULL; currentchanold = currentchanold->next ) { currentchannew = newinfo->channels; for ( ; currentchannew != NULL; currentchannew = currentchannew->next ) if ( stricmp(currentchannew->channel, currentchanold->channel) == 0 ) break; if (currentchannew == NULL) { do_query( "DELETE FROM " AJOIN_DBTAB " WHERE id = %u", currentchanold->db_id ); myResult = mysql_store_result(mysql); mysql_free_result(myResult); } } } free_ajoin_info(oldinfo); return MOD_CONT; } /* End update_ajoin_list() */ /************************************************************************** * db_get_chan(): Returns an AjoinChanList containing one channel record * from the database for a user. */ AjoinChanList *db_get_chan(char *nick, char *channel) { MYSQL_RES *myResult; char *tempstr = NULL; AjoinInfo *info; AjoinChanList *chan = NULL; info = get_ajoin_info(nick); tempstr = quote(channel); do_query( "SELECT id, ajoin_info_id, channel, chankey FROM " AJOIN_DBTAB " WHERE ajoin_info_id=%u AND channel='%s'", info->db_id, tempstr); free(tempstr); free_ajoin_info(info); myResult = mysql_store_result(mysql); if (mysql_num_rows(myResult) == 1) chan = fill_chan_info(mysql_fetch_row(myResult)); mysql_free_result(myResult); return chan; } /* End db_get_chan() */ /************************************************************************** * db_add_chan(): Adds a channel to the database. */ void db_add_chan(uint32 info_id, char *channel, char *key) { MYSQL_RES *myResult; char *escapedname, *escapedkey; escapedname = quote(channel); escapedkey = quote(key); do_query( "INSERT INTO " AJOIN_DBTAB " ( ajoin_info_id, channel, chankey ) " "VALUES ( %u, '%s', '%s' )", info_id, escapedname, escapedkey); myResult = mysql_store_result(mysql); mysql_free_result(myResult); free(escapedname); free(escapedkey); } /* End db_add_chan() */ /************************************************************************** * db_del_chan(): Removes a channel from the database. */ void db_del_chan(AjoinChanList *chan) { MYSQL_RES *myResult; char *tempstr; tempstr = quote(chan->channel); do_query( "DELETE FROM " AJOIN_DBTAB " WHERE ( ajoin_info_id = %u ) AND " "( channel = '%s' )", chan->info_id, tempstr); free(tempstr); myResult = mysql_store_result(mysql); mysql_free_result(myResult); } /* End db_del_chan() */ /************************************************************************** * db_change_group_keys(): Takes a User and an AjoinChanList and sets all * of the grouped nicknames' corresponding channel keys to the one in * the AjoinChanList. */ void db_change_group_keys(User *u, char *channel, char *newkey) { char **ids, *tempstr, *escapedname, *escapedkey; MYSQL_RES *myResult; ids = group_info_ids(u); tempstr = make_sql_list(ids); free(ids); escapedname = quote(channel); escapedkey = quote(newkey); do_query( "UPDATE " AJOIN_DBTAB " SET chankey='%s' WHERE ( channel='%s' ) " "AND ( ajoin_info_id IN ( %s ) )", escapedkey, escapedname, tempstr ); free(tempstr); free(escapedname); free(escapedkey); myResult = mysql_store_result(mysql); mysql_free_result(myResult); } /* End db_change_group_keys() */ /*************************************************************************** * db_del_group_chan(): Removes a channel from the database where it's * owned by anyone in the user's group. */ uint32 db_del_group_chan(User *u, char *channel) { MYSQL_RES *myResult; char **ids, *tempstr1, *tempstr2; uint32 num_affected_rows; tempstr1 = quote(channel); ids = group_info_ids(u); tempstr2 = make_sql_list(ids); free(ids); do_query( "DELETE FROM " AJOIN_DBTAB " WHERE ( channel = '%s' ) AND " "( ajoin_info_id IN ( %s ) )", tempstr1, tempstr2); free(tempstr1); free(tempstr2); myResult = mysql_store_result(mysql); num_affected_rows = (uint32) mysql_affected_rows(mysql); mysql_free_result(myResult); return num_affected_rows; } /* End db_del_group_chan() */ /*************************************************************************** * db_clear_list(): Deletes all channels associated with a nickname's * ajoin list. */ void db_clear_list(char *nick) { MYSQL_RES *myResult; AjoinInfo *info; info = get_ajoin_info(nick); do_query("DELETE FROM " AJOIN_DBTAB " WHERE ajoin_info_id = %u", info->db_id); myResult = mysql_store_result(mysql); mysql_free_result(myResult); free_ajoin_info(info); } /* End db_clear_list() */ /*************************************************************************** * do_query(): Perform a database query, consisting of the specified query * string which is a printf()-style format string and variable list. Be * sure that the query string and sub-strings are properly escaped before * calling this function. */ int do_query(const char *fmt, ... ) { va_list args; #ifdef __GNU_SOURCE char *query = NULL; #else char query[MAX_SQL_BUF]; #endif int result, slen; va_start(args, fmt); #ifdef __GNU_SOURCE vasprintf( &query, #else *query = '\0'; vsnprintf( query, MAX_SQL_BUF, #endif fmt, args ); #if 0 /* This just fixes vim's unmatched parenthesis syntax highlight */ ) #endif va_end(args); #ifdef __GNU_SOURCE if (!query) #else if (*query == '\0') #endif return -1; slen = strlen(query); if (debug) alog("[%s] debug: executing mysql_ping()", MODNAME); mysql_ping(mysql); /* checks for mysql connection and reconnect */ if (debug) alog("[%s] debug: Sending SQL query: %s", MODNAME, query); result = mysql_real_query(mysql, query , slen); #ifdef __GNU_SOURCE free(query); #endif if (result) { log_perror(mysql_error(mysql)); return result; } return 0; } /* End do_query() */ /******************************************************* * sub_display_error(): Puts an informative error in the * log as to why the init failed. */ int sub_display_error(void) { alog("[%s] Can't connect to MySQL: %s\n", MODNAME, mysql_error(mysql)); alog("%s: unloading", MODNAME); return MOD_STOP; } /* End sub_display_error() */ /******************************************************* * mydb_init_sub(): Checks for tables and creates them * if they don't exist. */ void mydb_init_sub(const char *tablename, const char *schema) { MYSQL_RES *res; do_query("SHOW TABLES LIKE '%s'", tablename); res = mysql_store_result(mysql); if (mysql_num_rows(res) == 0) { do_query(schema); alog("[%s] Created table %s", MODNAME, tablename); } mysql_free_result(res); } /* End mydb_init_sub() */ /*************************************************************************** * mydb_init(): Attempt a connection to the MySQL database and handle * any errors that may happen during that connection. If the connection * succeeds, the global variable "mysql" contains a valid connection. * * A check is also done to make sure that the tables needed by this module * exist in the database. If they don't, they are created. */ int mydb_init(void) { /* initializing mysql connection */ mysql = mysql_init(NULL); if (MysqlSock) { if ((!mysql_real_connect(mysql, MysqlHost, MysqlUser, MysqlPass, MysqlName, MysqlPort, MysqlSock, 0))) return sub_display_error(); } else { if ((!mysql_real_connect(mysql, MysqlHost, MysqlUser, MysqlPass, MysqlName, MysqlPort, NULL, 0))) return sub_display_error(); } if (mysql_select_db(mysql, MysqlName)) return sub_display_error(); /* checking for tables and creating them, if not found */ mydb_init_sub(AJOIN_DBTAB, AJOIN_SCHEMA); mydb_init_sub(SET_AJOIN_DBTAB, SET_AJOIN_SCHEMA); return 0; } /* End mydb_init() */ /*************************************************************************** * purgedb_sub(): SQL query parts of purgedb. */ void purgedb_sub( const char *table1, const char *compare1, const char *table2, const char *compare2 ) { MYSQL_RES *myResult; do_query( "DELETE %s.* FROM %s LEFT JOIN %s ON %s.%s = %s.%s " "WHERE %s.%s IS NULL", table1, table1, table2, table1, compare1, table2, compare2, table2, compare2 ); myResult = mysql_store_result(mysql); if ( (debug) && (mysql_affected_rows(mysql) > 0) ) alog( "[%s] Purged %lu old entries from database table %s", MODNAME, (unsigned long) mysql_affected_rows(mysql), table1 ); mysql_free_result(myResult); } /* End purgedb_sub() */ /************************************************************************** * purgedb(): If users are dropped or expire from Anope's main database * tables, this function deletes the records pertaining to those users * from our tables. * * This function is inserted into Anope's moduleCallBack to be called at * regular intervals instead of on specific command events. When it's * finished, moduleCallBack() is used to add itself to the next interval. */ int purgedb(int argc, char *argv[]) { purgedb_sub(SET_AJOIN_DBTAB, "nick", "anope_ns_alias", "nick"); purgedb_sub(AJOIN_DBTAB, "ajoin_info_id", SET_AJOIN_DBTAB, "id"); moduleAddCallback( "NSAJOINPURGEDB", time(NULL) + dotime(PURGETIME), purgedb, 0, NULL ); return MOD_CONT; } /* End purgedb() */ /*************************************************************************** * db_keepalive(): This function is a kludge, put in place as a workaround * for a problem where the MySQL server would close the connection after * periods of idleness and mysql_ping() was causing us to receive a fatal * SIGPIPE signal instead of just re-establishing the connection. When I * figure out how to prevent this problem in another way, this function adds * a callback at a 5 minute interval which calls mysql_ping() to keep the * connection alive. */ int db_keepalive(int argc, char *argv[]) { mysql_ping(mysql); moduleAddCallback( "NSAJOINKEEPALIVE", time(NULL) + dotime("1h"), db_keepalive, 0, NULL ); return MOD_CONT; } /* End db_keepalive() */ /************************* END OF FILE ***********************************/