diff --git a/Makefile.windows b/Makefile.windows index f63f1ec92..0ef07a5a7 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -309,7 +309,8 @@ DLL_FILES=SRC/MODULES/CLOAK.DLL \ SRC/MODULES/CHATHISTORY.DLL \ SRC/MODULES/TARGETFLOODPROT.DLL \ SRC/MODULES/TYPING-INDICATOR.DLL \ - SRC/MODULES/CLIENTTAGDENY.DLL + SRC/MODULES/CLIENTTAGDENY.DLL \ + SRC/MODULES/WATCH-BACKEND.DLL ALL: CONF UNREALSVC.EXE UnrealIRCd.exe MODULES @@ -1126,5 +1127,11 @@ src/modules/typing-indicator.dll: src/modules/typing-indicator.c $(INCLUDES) src/modules/clienttagdeny.dll: src/modules/clienttagdeny.c $(INCLUDES) $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/clienttagdeny.c $(MODLFLAGS) + +src/modules/watch-backend.dll: src/modules/watch-backend.c $(INCLUDES) + $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/watch-backend.c $(MODLFLAGS) + +src/modules/monitor.dll: src/modules/monitor.c $(INCLUDES) + $(CC) $(MODCFLAGS) /Fosrc/modules/ /Fesrc/modules/ src/modules/monitor.c $(MODLFLAGS) dummy: diff --git a/doc/conf/modules.default.conf b/doc/conf/modules.default.conf index 28dd7f972..adcc60057 100644 --- a/doc/conf/modules.default.conf +++ b/doc/conf/modules.default.conf @@ -213,6 +213,7 @@ loadmodule "sts"; /* strict transport policy (set::tls::sts-policy) */ loadmodule "link-security"; /* link-security announce */ loadmodule "plaintext-policy"; /* plaintext-policy announce */ loadmodule "chathistory"; /* CHATHISTORY client command, 005 and a CAP (draft) */ +loadmodule "monitor"; /* MONITOR command with functionality similar to WATCH */ /*** Other ***/ @@ -235,3 +236,4 @@ loadmodule "connthrottle"; /* see https://www.unrealircd.org/docs/Connthrottle * loadmodule "userip-tag"; /* unrealircd.org/userip tag for ircops */ loadmodule "userhost-tag"; /* unrealircd.org/userhost tag for ircops */ loadmodule "targetfloodprot"; /* set::anti-flood::target-flood protection */ +loadmodule "watch-backend"; /* used by watch and other modules */ diff --git a/include/h.h b/include/h.h index 0eef51998..f4970ff96 100644 --- a/include/h.h +++ b/include/h.h @@ -336,7 +336,6 @@ extern void del_queries(char *); /* Hash stuff */ #define NICK_HASH_TABLE_SIZE 32768 #define CHAN_HASH_TABLE_SIZE 32768 -#define WATCH_HASH_TABLE_SIZE 32768 #define WHOWAS_HASH_TABLE_SIZE 32768 #define THROTTLING_HASH_TABLE_SIZE 8192 extern uint64_t siphash(const char *in, const char *k); @@ -351,12 +350,6 @@ extern int add_to_id_hash_table(char *, Client *); extern int del_from_id_hash_table(char *, Client *); extern int add_to_channel_hash_table(char *, Channel *); extern void del_from_channel_hash_table(char *, Channel *); -extern int add_to_watch_hash_table(char *, Client *, int); -extern int del_from_watch_hash_table(char *, Client *); -extern int hash_check_watch(Client *, int); -extern int hash_del_watch_list(Client *); -extern void count_watch_memory(int *, u_long *); -extern Watch *hash_get_watch(char *); extern Channel *hash_get_chan_bucket(uint64_t); extern Client *hash_find_client(const char *, Client *); extern Client *hash_find_id(const char *, Client *); @@ -777,6 +770,11 @@ extern MODVAR void *(*labeled_response_save_context)(void); extern MODVAR void (*labeled_response_set_context)(void *ctx); extern MODVAR void (*labeled_response_force_end)(void); extern MODVAR void (*kick_user)(MessageTag *mtags, Channel *channel, Client *client, Client *victim, char *comment); +extern MODVAR int (*watch_add)(char *nick, Client *client, int flags); +extern MODVAR int (*watch_del)(char *nick, Client *client, int flags); +extern MODVAR int (*watch_del_list)(Client *client, int flags); +extern MODVAR Watch *(*watch_get)(char *nick); +extern MODVAR int (*watch_check)(Client *client, int reply); extern MODVAR char *(*tkl_uhost)(TKL *tkl, char *buf, size_t buflen, int options); /* /Efuncs */ diff --git a/include/modules.h b/include/modules.h index 66afae53d..8aa03fc93 100644 --- a/include/modules.h +++ b/include/modules.h @@ -24,7 +24,7 @@ #define MAXCUSTOMHOOKS 30 #define MAXHOOKTYPES 150 #define MAXCALLBACKS 30 -#define MAXEFUNCTIONS 90 +#define MAXEFUNCTIONS 128 #if defined(_WIN32) #define MOD_EXTENSION "dll" #define DLLFUNC _declspec(dllexport) @@ -1165,6 +1165,12 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va #define HOOKTYPE_CONNECT_EXTINFO 104 /** See hooktype_is_invited() */ #define HOOKTYPE_IS_INVITED 105 +/** See hooktype_post_local_nickchange() */ +#define HOOKTYPE_POST_LOCAL_NICKCHANGE 106 +/** See hooktype_post_remote_nickchange() */ +#define HOOKTYPE_POST_REMOTE_NICKCHANGE 107 +/** See hooktype_watch_notify() */ +#define HOOKTYPE_WATCH_NOTIFICATION 108 /* Adding a new hook here? * 1) Add the #define HOOKTYPE_.... with a new number * 2) Add a hook prototype (see below) @@ -1523,9 +1529,10 @@ int hooktype_modechar_add(Channel *channel, int modechar); * @param client The client * @param mtags Message tags associated with the event * @param reason The away reason, or NULL if away is unset. + * @param already_as_away Set to 1 if the user only changed their away reason. * @return The return value is ignored (use return 0) */ -int hooktype_away(Client *client, MessageTag *mtags, char *reason); +int hooktype_away(Client *client, MessageTag *mtags, char *reason, int already_as_away); /** Called when a user wants to invite another user to a channel (function prototype for HOOKTYPE_PRE_INVITE). * @param client The client @@ -2114,6 +2121,29 @@ int hooktype_connect_extinfo(Client *client, NameValuePrioList **list); */ int hooktype_is_invited(Client *client, Channel *channel, int *invited); +/** Called after a local user has changed the nick name (function prototype for HOOKTYPE_POST_LOCAL_NICKCHANGE). + * @param client The client + * @param mtags Message tags associated with the event + * @return The return value is ignored (use return 0) + */ +int hooktype_post_local_nickchange(Client *client, MessageTag *mtags); + +/** Called after a remote user has changed the nick name (function prototype for HOOKTYPE_POST_REMOTE_NICKCHANGE). + * @param client The client + * @param mtags Message tags associated with the event + * @return The return value is ignored (use return 0) + */ +int hooktype_post_remote_nickchange(Client *client, MessageTag *mtags); + +/** Called when a user changed its state in a way that should trigger a WATCH notification (function prototype for HOOKTYPE_WATCH_NOTIFY). + * @param client The client whose state has changed + * @param watch The watch list entry + * @param lp The associated watch list entry for WATCHing user + * @param reply The numeric that is supposed to be sent as a notification (module-defined) + * @return The return value is ignored (use return 0) + */ +int hooktype_watch_notification(Client *client, Watch *watch, Link *lp, int reply); + /** @} */ #ifdef GCC_TYPECHECKING @@ -2224,7 +2254,10 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum ((hooktype == HOOKTYPE_ACCOUNT_LOGIN) && !ValidateHook(hooktype_account_login, func)) || \ ((hooktype == HOOKTYPE_CLOSE_CONNECTION) && !ValidateHook(hooktype_close_connection, func)) || \ ((hooktype == HOOKTYPE_CONNECT_EXTINFO) && !ValidateHook(hooktype_connect_extinfo, func)) || \ - ((hooktype == HOOKTYPE_IS_INVITED) && !ValidateHook(hooktype_is_invited, func)) ) \ + ((hooktype == HOOKTYPE_IS_INVITED) && !ValidateHook(hooktype_is_invited, func)) || \ + ((hooktype == HOOKTYPE_POST_LOCAL_NICKCHANGE) && !ValidateHook(hooktype_post_local_nickchange, func)) || \ + ((hooktype == HOOKTYPE_POST_REMOTE_NICKCHANGE) && !ValidateHook(hooktype_post_remote_nickchange, func)) || \ + ((hooktype == HOOKTYPE_WATCH_NOTIFICATION) && !ValidateHook(hooktype_watch_notification, func)) )\ _hook_error_incompatible(); #endif /* GCC_TYPECHECKING */ @@ -2335,6 +2368,11 @@ enum EfunctionType { EFUNC_LABELED_RESPONSE_SET_CONTEXT, EFUNC_LABELED_RESPONSE_FORCE_END, EFUNC_KICK_USER, + EFUNC_WATCH_ADD, + EFUNC_WATCH_DEL, + EFUNC_WATCH_DEL_LIST, + EFUNC_WATCH_GET, + EFUNC_WATCH_CHECK, EFUNC_TKL_UHOST, }; diff --git a/include/numeric.h b/include/numeric.h index fce426860..e291a963c 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -375,6 +375,12 @@ #define RPL_WHOISSECURE 671 +#define RPL_MONONLINE 730 +#define RPL_MONOFFLINE 731 +#define RPL_MONLIST 732 +#define RPL_ENDOFMONLIST 733 +#define ERR_MONLISTFULL 734 + #define ERR_MLOCKRESTRICTED 742 #define ERR_CANNOTDOCOMMAND 972 diff --git a/include/struct.h b/include/struct.h index 9ebac0bc0..b4008c628 100644 --- a/include/struct.h +++ b/include/struct.h @@ -1353,8 +1353,6 @@ struct LocalClient { u_short sendB; /**< Statistics: counters to count upto 1-k lots of bytes */ u_short receiveB; /**< Statistics: sent and received (???) */ short lastsq; /**< # of 2k blocks when sendqueued called last */ - Link *watch; /**< Watch notification list (WATCH) for this user */ - u_short watches; /**< Number of entries in the watch list */ ModData moddata[MODDATA_MAX_LOCAL_CLIENT]; /**< LocalClient attached module data, used by the ModData system */ #ifdef DEBUGMODE time_t cputime; /**< Something with debugging (why is this a time_t? TODO) */ @@ -2001,6 +1999,15 @@ struct Mode { char key[KEYLEN + 1]; /**< The +k key in effect (eg: secret), if any - otherwise NULL */ }; +/* flags for Link if used to contain Watch --k4be */ + +/* WATCH type */ +#define WATCH_FLAG_TYPE_WATCH (1<<0) /* added via /WATCH command */ +#define WATCH_FLAG_TYPE_MONITOR (1<<1) /* added via /MONITOR command */ + +/* behaviour switches */ +#define WATCH_FLAG_AWAYNOTIFY (1<<8) /* should send AWAY notifications */ + /* Used for notify-hash buckets... -Donwulff */ struct Watch { diff --git a/src/api-efunctions.c b/src/api-efunctions.c index 290c410a2..a5a6e3e1e 100644 --- a/src/api-efunctions.c +++ b/src/api-efunctions.c @@ -123,6 +123,11 @@ void *(*labeled_response_save_context)(void); void (*labeled_response_set_context)(void *ctx); void (*labeled_response_force_end)(void); void (*kick_user)(MessageTag *mtags, Channel *channel, Client *client, Client *victim, char *comment); +int (*watch_add)(char *nick, Client *client, int flags); +int (*watch_del)(char *nick, Client *client, int flags); +int (*watch_del_list)(Client *client, int flags); +Watch *(*watch_get)(char *nick); +int (*watch_check)(Client *client, int reply); Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*cfunc)()) { @@ -279,6 +284,12 @@ void efunctions_switchover(void) void efunc_init_function_(EfunctionType what, char *name, void *func, void *default_func) { + if (what >= MAXEFUNCTIONS) + { + /* increase MAXEFUNCTIONS if you ever encounter that --k4be */ + ircd_log(LOG_ERROR, "Too many efunctions!"); + abort(); + } safe_strdup(efunction_table[what].name, name); efunction_table[what].funcptr = func; efunction_table[what].deffunc = default_func; @@ -368,5 +379,11 @@ void efunctions_init(void) efunc_init_function(EFUNC_LABELED_RESPONSE_SET_CONTEXT, labeled_response_set_context, labeled_response_set_context_default_handler); efunc_init_function(EFUNC_LABELED_RESPONSE_FORCE_END, labeled_response_force_end, labeled_response_force_end_default_handler); efunc_init_function(EFUNC_KICK_USER, kick_user, NULL); + efunc_init_function(EFUNC_WATCH_ADD, watch_add, NULL); + efunc_init_function(EFUNC_WATCH_DEL, watch_del, NULL); + efunc_init_function(EFUNC_WATCH_DEL_LIST, watch_del_list, NULL); + efunc_init_function(EFUNC_WATCH_GET, watch_get, NULL); + efunc_init_function(EFUNC_WATCH_CHECK, watch_check, NULL); efunc_init_function(EFUNC_TKL_UHOST, tkl_uhost, NULL); } + diff --git a/src/hash.c b/src/hash.c index 1ef7d1cfd..90edc278a 100644 --- a/src/hash.c +++ b/src/hash.c @@ -260,11 +260,9 @@ void siphash_generate_key(char *k) static struct list_head clientTable[NICK_HASH_TABLE_SIZE]; static struct list_head idTable[NICK_HASH_TABLE_SIZE]; static Channel *channelTable[CHAN_HASH_TABLE_SIZE]; -static Watch *watchTable[WATCH_HASH_TABLE_SIZE]; static char siphashkey_nick[SIPHASH_KEY_LENGTH]; static char siphashkey_chan[SIPHASH_KEY_LENGTH]; -static char siphashkey_watch[SIPHASH_KEY_LENGTH]; static char siphashkey_whowas[SIPHASH_KEY_LENGTH]; static char siphashkey_throttling[SIPHASH_KEY_LENGTH]; @@ -277,7 +275,6 @@ void init_hash(void) siphash_generate_key(siphashkey_nick); siphash_generate_key(siphashkey_chan); - siphash_generate_key(siphashkey_watch); siphash_generate_key(siphashkey_whowas); siphash_generate_key(siphashkey_throttling); @@ -288,7 +285,6 @@ void init_hash(void) INIT_LIST_HEAD(&idTable[i]); memset(channelTable, 0, sizeof(channelTable)); - memset(watchTable, 0, sizeof(watchTable)); memset(ThrottlingHash, 0, sizeof(ThrottlingHash)); /* do not call init_throttling() here, as @@ -310,11 +306,6 @@ uint64_t hash_channel_name(const char *name) return siphash_nocase(name, siphashkey_chan) % CHAN_HASH_TABLE_SIZE; } -uint64_t hash_watch_nick_name(const char *name) -{ - return siphash_nocase(name, siphashkey_watch) % WATCH_HASH_TABLE_SIZE; -} - uint64_t hash_whowas_name(const char *name) { return siphash_nocase(name, siphashkey_whowas) % WHOWAS_HASH_TABLE_SIZE; @@ -597,303 +588,6 @@ Channel *hash_get_chan_bucket(uint64_t hashv) return channelTable[hashv]; } -void count_watch_memory(int *count, u_long *memory) -{ - int i = WATCH_HASH_TABLE_SIZE; - Watch *anptr; - - while (i--) - { - anptr = watchTable[i]; - while (anptr) - { - (*count)++; - (*memory) += sizeof(Watch)+strlen(anptr->nick); - anptr = anptr->hnext; - } - } -} - -/* - * add_to_watch_hash_table - */ -int add_to_watch_hash_table(char *nick, Client *client, int awaynotify) -{ - unsigned int hashv; - Watch *anptr; - Link *lp; - - - /* Get the right bucket... */ - hashv = hash_watch_nick_name(nick); - - /* Find the right nick (header) in the bucket, or NULL... */ - if ((anptr = (Watch *)watchTable[hashv])) - while (anptr && mycmp(anptr->nick, nick)) - anptr = anptr->hnext; - - /* If found NULL (no header for this nick), make one... */ - if (!anptr) { - anptr = (Watch *)safe_alloc(sizeof(Watch)+strlen(nick)); - anptr->lasttime = timeofday; - strcpy(anptr->nick, nick); - - anptr->watch = NULL; - - anptr->hnext = watchTable[hashv]; - watchTable[hashv] = anptr; - } - /* Is this client already on the watch-list? */ - if ((lp = anptr->watch)) - while (lp && (lp->value.client != client)) - lp = lp->next; - - /* No it isn't, so add it in the bucket and client addint it */ - if (!lp) { - lp = anptr->watch; - anptr->watch = make_link(); - anptr->watch->value.client = client; - anptr->watch->flags = awaynotify; - anptr->watch->next = lp; - - lp = make_link(); - lp->next = client->local->watch; - lp->value.wptr = anptr; - lp->flags = awaynotify; - client->local->watch = lp; - client->local->watches++; - } - - return 0; -} - -/* - * hash_check_watch - */ -int hash_check_watch(Client *client, int reply) -{ - unsigned int hashv; - Watch *anptr; - Link *lp; - int awaynotify = 0; - - if ((reply == RPL_GONEAWAY) || (reply == RPL_NOTAWAY) || (reply == RPL_REAWAY)) - awaynotify = 1; - - /* Get us the right bucket */ - hashv = hash_watch_nick_name(client->name); - - /* Find the right header in this bucket */ - if ((anptr = (Watch *)watchTable[hashv])) - while (anptr && mycmp(anptr->nick, client->name)) - anptr = anptr->hnext; - if (!anptr) - return 0; /* This nick isn't on watch */ - - /* Update the time of last change to item */ - anptr->lasttime = TStime(); - - /* Send notifies out to everybody on the list in header */ - for (lp = anptr->watch; lp; lp = lp->next) - { - if (!awaynotify) - { - sendnumeric(lp->value.client, reply, - client->name, - (IsUser(client) ? client->user->username : ""), - (IsUser(client) ? - (IsHidden(client) ? client->user->virthost : client-> - user->realhost) : ""), anptr->lasttime, client->info); - } - else - { - /* AWAY or UNAWAY */ - if (!lp->flags) - continue; /* skip away/unaway notification for users not interested in them */ - - if (reply == RPL_NOTAWAY) - sendnumeric(lp->value.client, reply, - client->name, - (IsUser(client) ? client->user->username : ""), - (IsUser(client) ? - (IsHidden(client) ? client->user->virthost : client-> - user->realhost) : ""), client->user->lastaway); - else /* RPL_GONEAWAY / RPL_REAWAY */ - sendnumeric(lp->value.client, reply, - client->name, - (IsUser(client) ? client->user->username : ""), - (IsUser(client) ? - (IsHidden(client) ? client->user->virthost : client-> - user->realhost) : ""), client->user->lastaway, client->user->away); - } - } - - return 0; -} - -/* - * hash_get_watch - */ -Watch *hash_get_watch(char *nick) -{ - unsigned int hashv; - Watch *anptr; - - hashv = hash_watch_nick_name(nick); - - if ((anptr = (Watch *)watchTable[hashv])) - while (anptr && mycmp(anptr->nick, nick)) - anptr = anptr->hnext; - - return anptr; -} - -/* - * del_from_watch_hash_table - */ -int del_from_watch_hash_table(char *nick, Client *client) -{ - unsigned int hashv; - Watch *anptr, *nlast = NULL; - Link *lp, *last = NULL; - - /* Get the bucket for this nick... */ - hashv = hash_watch_nick_name(nick); - - /* Find the right header, maintaining last-link pointer... */ - if ((anptr = (Watch *)watchTable[hashv])) - while (anptr && mycmp(anptr->nick, nick)) { - nlast = anptr; - anptr = anptr->hnext; - } - if (!anptr) - return 0; /* No such watch */ - - /* Find this client from the list of notifies... with last-ptr. */ - if ((lp = anptr->watch)) - while (lp && (lp->value.client != client)) { - last = lp; - lp = lp->next; - } - if (!lp) - return 0; /* No such client to watch */ - - /* Fix the linked list under header, then remove the watch entry */ - if (!last) - anptr->watch = lp->next; - else - last->next = lp->next; - free_link(lp); - - /* Do the same regarding the links in client-record... */ - last = NULL; - if ((lp = client->local->watch)) - while (lp && (lp->value.wptr != anptr)) { - last = lp; - lp = lp->next; - } - - /* - * Give error on the odd case... probobly not even neccessary - * No error checking in ircd is unneccessary ;) -Cabal95 - */ - if (!lp) - sendto_ops("WATCH debug error: del_from_watch_hash_table " - "found a watch entry with no client " - "counterpoint processing nick %s on client %p!", - nick, client->user); - else { - if (!last) /* First one matched */ - client->local->watch = lp->next; - else - last->next = lp->next; - free_link(lp); - } - /* In case this header is now empty of notices, remove it */ - if (!anptr->watch) { - if (!nlast) - watchTable[hashv] = anptr->hnext; - else - nlast->hnext = anptr->hnext; - safe_free(anptr); - } - - /* Update count of notifies on nick */ - client->local->watches--; - - return 0; -} - -/* - * hash_del_watch_list - */ -int hash_del_watch_list(Client *client) -{ - unsigned int hashv; - Watch *anptr; - Link *np, *lp, *last; - - - if (!(np = client->local->watch)) - return 0; /* Nothing to do */ - - client->local->watch = NULL; /* Break the watch-list for client */ - while (np) { - /* Find the watch-record from hash-table... */ - anptr = np->value.wptr; - last = NULL; - for (lp = anptr->watch; lp && (lp->value.client != client); - lp = lp->next) - last = lp; - - /* Not found, another "worst case" debug error */ - if (!lp) - sendto_ops("WATCH Debug error: hash_del_watch_list " - "found a WATCH entry with no table " - "counterpoint processing client %s!", - client->name); - else { - /* Fix the watch-list and remove entry */ - if (!last) - anptr->watch = lp->next; - else - last->next = lp->next; - free_link(lp); - - /* - * If this leaves a header without notifies, - * remove it. Need to find the last-pointer! - */ - if (!anptr->watch) { - Watch *np2, *nl; - - hashv = hash_watch_nick_name(anptr->nick); - - nl = NULL; - np2 = watchTable[hashv]; - while (np2 != anptr) { - nl = np2; - np2 = np2->hnext; - } - - if (nl) - nl->hnext = anptr->hnext; - else - watchTable[hashv] = anptr->hnext; - safe_free(anptr); - } - } - - lp = np; /* Save last pointer processed */ - np = np->next; /* Jump to the next pointer */ - free_link(lp); /* Free the previous */ - } - - client->local->watches = 0; - - return 0; -} - /* Throttling - originally by Stskeeps */ /* Note that we call this set::anti-flood::connect-flood nowadays */ diff --git a/src/misc.c b/src/misc.c index 82cd28370..042f3edb7 100644 --- a/src/misc.c +++ b/src/misc.c @@ -543,8 +543,6 @@ static void exit_one_client(Client *client, MessageTag *mtags_i, const char *com } if (*client->name) del_from_client_hash_table(client->name, client); - if (IsUser(client)) - hash_check_watch(client, RPL_LOGOFF); if (remote_rehash_client == client) remote_rehash_client = NULL; /* client did a /REHASH and QUIT before rehash was complete */ remove_client_from_list(client); @@ -634,8 +632,7 @@ void exit_client_ex(Client *client, Client *origin, MessageTag *recv_mtags, char log_data_string("extended_client_info", get_connect_extinfo(client)), log_data_string("reason", comment), log_data_integer("connected_time", connected_time)); - /* Clean out list and watch structures -Donwulff */ - hash_del_watch_list(client); + } else if (IsUnknown(client)) { diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in index cdc560f04..628b80f94 100644 --- a/src/modules/Makefile.in +++ b/src/modules/Makefile.in @@ -74,7 +74,8 @@ R_MODULES= \ bot-tag.so \ reply-tag.so typing-indicator.so \ ident_lookup.so history.so chathistory.so \ - targetfloodprot.so clienttagdeny.so + targetfloodprot.so clienttagdeny.so watch-backend.so \ + monitor.so MODULES=cloak.so $(R_MODULES) MODULEFLAGS=@MODULEFLAGS@ @@ -653,6 +654,14 @@ clienttagdeny.so: clienttagdeny.c $(INCLUDES) $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ -o clienttagdeny.so clienttagdeny.c +watch-backend.so: watch-backend.c $(INCLUDES) + $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ + -o watch-backend.so watch-backend.c + +monitor.so: monitor.c $(INCLUDES) + $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ + -o monitor.so monitor.c + ############################################################################# # capabilities ############################################################################# diff --git a/src/modules/away.c b/src/modules/away.c index a760c83c1..e8ee3240e 100644 --- a/src/modules/away.c +++ b/src/modules/away.c @@ -73,10 +73,9 @@ CMD_FUNC(cmd_away) new_message(client, recv_mtags, &mtags); sendto_server(client, 0, 0, mtags, ":%s AWAY", client->name); - hash_check_watch(client, RPL_NOTAWAY); sendto_local_common_channels(client, client, ClientCapabilityBit("away-notify"), mtags, ":%s AWAY", client->name); - RunHook3(HOOKTYPE_AWAY, client, mtags, NULL); + RunHook4(HOOKTYPE_AWAY, client, mtags, NULL, 0); free_message_tags(mtags); } @@ -125,13 +124,11 @@ CMD_FUNC(cmd_away) if (MyConnect(client)) sendnumeric(client, RPL_NOWAWAY); - hash_check_watch(client, already_as_away ? RPL_REAWAY : RPL_GONEAWAY); - sendto_local_common_channels(client, client, ClientCapabilityBit("away-notify"), mtags, ":%s AWAY :%s", client->name, client->user->away); - RunHook3(HOOKTYPE_AWAY, client, mtags, client->user->away); + RunHook4(HOOKTYPE_AWAY, client, mtags, client->user->away, already_as_away); free_message_tags(mtags); diff --git a/src/modules/monitor.c b/src/modules/monitor.c new file mode 100644 index 000000000..106e267a6 --- /dev/null +++ b/src/modules/monitor.c @@ -0,0 +1,223 @@ +/* + * IRC - Internet Relay Chat, src/modules/monitor.c + * (C) 2021 The UnrealIRCd Team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "unrealircd.h" + +#define MSG_MONITOR "MONITOR" + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +CMD_FUNC(cmd_monitor); +char *monitor_isupport_param(void); +int monitor_nickchange(Client *client, MessageTag *mtags, char *newnick); +int monitor_post_nickchange(Client *client, MessageTag *mtags); +int monitor_quit(Client *client, MessageTag *mtags, char *comment); +int monitor_connect(Client *client); +int monitor_notification(Client *client, Watch *watch, Link *lp, int reply); + +ModuleHeader MOD_HEADER + = { + "monitor", + "5.0", + "command /monitor", + "UnrealIRCd Team", + "unrealircd-5", + }; + +MOD_INIT() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + + CommandAdd(modinfo->handle, MSG_MONITOR, cmd_monitor, 2, CMD_USER); + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_NICKCHANGE, 0, monitor_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_REMOTE_NICKCHANGE, 0, monitor_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_POST_LOCAL_NICKCHANGE, 0, monitor_post_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_POST_REMOTE_NICKCHANGE, 0, monitor_post_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_REMOTE_QUIT, 0, monitor_quit); + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, monitor_quit); + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, monitor_connect); + HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, monitor_connect); + HookAdd(modinfo->handle, HOOKTYPE_WATCH_NOTIFICATION, 0, monitor_notification); + + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + ISupportAdd(modinfo->handle, "MONITOR", monitor_isupport_param()); + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +char *monitor_isupport_param(void) +{ + /* i find it unlikely for a client to use WATCH and MONITOR at the same time, so keep a single limit for both */ + return STR(MAXWATCH); +} + +int monitor_nickchange(Client *client, MessageTag *mtags, char *newnick) +{ + if(!smycmp(client->name, newnick)) // new nick is same as old one, maybe the case changed + return 0; + + watch_check(client, RPL_MONOFFLINE); + return 0; +} + +int monitor_post_nickchange(Client *client, MessageTag *mtags) +{ + watch_check(client, RPL_MONONLINE); + return 0; +} + +int monitor_quit(Client *client, MessageTag *mtags, char *comment) +{ + watch_check(client, RPL_MONOFFLINE); + return 0; +} + +int monitor_connect(Client *client) +{ + watch_check(client, RPL_MONONLINE); + return 0; +} + +int monitor_notification(Client *client, Watch *watch, Link *lp, int reply) +{ + if (!(lp->flags & WATCH_FLAG_TYPE_MONITOR)) + return 0; + + switch (reply) + { + case RPL_MONONLINE: + sendnumeric(lp->value.client, RPL_MONONLINE, client->name, client->user->username, GetHost(client)); + break; + case RPL_MONOFFLINE: + sendnumeric(lp->value.client, RPL_MONOFFLINE, client->name); + break; + default: + break; /* may be handled by other modules */ + } + + return 0; +} + +void send_status(Client *client, MessageTag *recv_mtags, char *nick) +{ + MessageTag *mtags = NULL; + Client *user; + user = find_person(nick, NULL); + new_message(client, recv_mtags, &mtags); + if(!user){ + sendnumeric(client, RPL_MONOFFLINE, nick); + } else { + sendnumeric(client, RPL_MONONLINE, user->name, user->user->username, GetHost(user)); + } + free_message_tags(mtags); +} + +#define WATCHES(client) (moddata_local_client(client, watchCounterMD).i) +#define WATCH(client) (moddata_local_client(client, watchListMD).ptr) + +CMD_FUNC(cmd_monitor) +{ + char cmd; + char *s, *p = NULL; + int i; + int toomany = 0; + Link *lp; + + if(parc < 2 || BadPtr(parv[1])) + cmd = 'l'; + else + cmd = tolower(*parv[1]); + + ModDataInfo *watchCounterMD = findmoddata_byname("watchCount", MODDATATYPE_LOCAL_CLIENT); + ModDataInfo *watchListMD = findmoddata_byname("watchList", MODDATATYPE_LOCAL_CLIENT); + + if (!watchCounterMD || !watchListMD) + { + ircd_log(LOG_WARNING, "monitor: moddata not available. Check `watch-backend` module."); + sendnotice(client, "MONITOR command is not available at this moment. Please try again later."); + return; + } + + switch(cmd) + { + case 'c': + watch_del_list(client, WATCH_FLAG_TYPE_MONITOR); + break; + case 'l': + lp = WATCH(client); + while (lp) + { + if (!(lp->flags & WATCH_FLAG_TYPE_MONITOR)) + { + lp = lp->next; + continue; /* this one is not ours */ + } + sendnumeric(client, RPL_MONLIST, lp->value.wptr->nick); + lp = lp->next; + } + + sendnumeric(client, RPL_ENDOFMONLIST); + break; + case 's': + lp = WATCH(client); + while (lp) + { + if (!(lp->flags & WATCH_FLAG_TYPE_MONITOR)) + { + lp = lp->next; + continue; /* this one is not ours */ + } + send_status(client, recv_mtags, lp->value.wptr->nick); + lp = lp->next; + } + break; + case '-': + case '+': + if(parc < 3 || BadPtr(parv[2])) + return; + for(s = strtoken(&p, parv[2], ","); s; s = strtoken(&p, NULL, ",")){ + if(cmd == '-'){ + watch_del(s, client, WATCH_FLAG_TYPE_MONITOR); + } else { + if (WATCHES(client) >= MAXWATCH) + { + sendnumeric(client, ERR_MONLISTFULL, MAXWATCH, s); + continue; + } + if (do_nick_name(s)) + watch_add(s, client, WATCH_FLAG_TYPE_MONITOR); + send_status(client, recv_mtags, s); + } + } + break; + } +} + diff --git a/src/modules/nick.c b/src/modules/nick.c index 98b76ff94..3d9f8bc94 100644 --- a/src/modules/nick.c +++ b/src/modules/nick.c @@ -227,12 +227,11 @@ CMD_FUNC(cmd_nick_remote) /* Finally set new nick name. */ del_from_client_hash_table(client->name, client); - hash_check_watch(client, RPL_LOGOFF); strcpy(client->name, nick); add_to_client_hash_table(nick, client); - hash_check_watch(client, RPL_LOGON); + RunHook2(HOOKTYPE_POST_REMOTE_NICKCHANGE, client, mtags); } CMD_FUNC(cmd_nick_local) @@ -243,7 +242,8 @@ CMD_FUNC(cmd_nick_local) char nick[NICKLEN + 2], descbuf[BUFSIZE]; Membership *mp; long lastnick = 0l; - int differ = 1, update_watch = 1; + int differ = 1; + int newuser = 0; unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0; Hook *h; int i = 0; @@ -353,6 +353,8 @@ CMD_FUNC(cmd_nick_local) /* New local client? */ if (!client->name[0]) { + newuser = 1; + if (iConf.ping_cookie) { /* @@ -389,7 +391,6 @@ CMD_FUNC(cmd_nick_local) */ } else { /* New user! */ - update_watch = 0; /* already done in register_user() */ strlcpy(nick, client->name, sizeof(nick)); /* don't ask, but I need this. do not remove! -- Syzop */ } } @@ -456,8 +457,6 @@ CMD_FUNC(cmd_nick_local) } del_from_client_hash_table(client->name, client); - if (update_watch && IsUser(client)) - hash_check_watch(client, RPL_LOGOFF); strlcpy(client->name, nick, sizeof(client->name)); add_to_client_hash_table(nick, client); @@ -466,11 +465,11 @@ CMD_FUNC(cmd_nick_local) snprintf(descbuf, sizeof(descbuf), "Client: %s", nick); fd_desc(client->local->fd, descbuf); - if (update_watch && IsUser(client)) - hash_check_watch(client, RPL_LOGON); - if (removemoder && MyUser(client)) sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name); + + if (MyUser(client) && !newuser) + RunHook2(HOOKTYPE_POST_LOCAL_NICKCHANGE, client, recv_mtags); } /* @@ -1099,7 +1098,6 @@ int _register_user(Client *client, char *nick, char *username, char *umode, char safe_strdup(client->user->virthost, virthost); } - hash_check_watch(client, RPL_LOGON); /* Uglier hack */ build_umode_string(client, 0, SEND_UMODES|UMODE_SERVNOTICE, buf); sendto_serv_butone_nickcmd(client->direction, client, (*buf == '\0' ? "+" : buf)); diff --git a/src/modules/svsnick.c b/src/modules/svsnick.c index 3a43e89a4..f2776c4b2 100644 --- a/src/modules/svsnick.c +++ b/src/modules/svsnick.c @@ -103,7 +103,6 @@ CMD_FUNC(cmd_svsnick) add_history(acptr, 1); del_from_client_hash_table(acptr->name, acptr); - hash_check_watch(acptr, RPL_LOGOFF); sendto_snomask(SNO_NICKCHANGE, "*** %s (%s@%s) has been forced to change their nickname to %s", @@ -111,5 +110,5 @@ CMD_FUNC(cmd_svsnick) strlcpy(acptr->name, parv[2], sizeof acptr->name); add_to_client_hash_table(parv[2], acptr); - hash_check_watch(acptr, RPL_LOGON); + RunHook2(HOOKTYPE_POST_LOCAL_NICKCHANGE, acptr, mtags); } diff --git a/src/modules/watch-backend.c b/src/modules/watch-backend.c new file mode 100644 index 000000000..5dca14c1a --- /dev/null +++ b/src/modules/watch-backend.c @@ -0,0 +1,378 @@ +/* + * IRC - Internet Relay Chat, src/modules/watch-backend.c + * (C) 2021 The UnrealIRCd Team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "unrealircd.h" + +#define WATCH_HASH_TABLE_SIZE 32768 + +#define WATCHES(client) (moddata_local_client(client, watchCounterMD).i) +#define WATCH(client) (moddata_local_client(client, watchListMD).ptr) + +ModDataInfo *watchCounterMD; +ModDataInfo *watchListMD; +static Watch *watchTable[WATCH_HASH_TABLE_SIZE]; +static int watch_initialized = 0; +static char siphashkey_watch[SIPHASH_KEY_LENGTH]; + +void dummy_free(ModData *md); +void watch_free(ModData *md); + +int watch_backend_user_quit(Client *client, MessageTag *mtags, char *comment); +int add_to_watch_hash_table(char *nick, Client *client, int flags); +int hash_check_watch(Client *client, int reply); +Watch *hash_get_watch(char *nick); +int del_from_watch_hash_table(char *nick, Client *client, int flags); +int hash_del_watch_list(Client *client, int flags); +uint64_t hash_watch_nick_name(const char *name); + +ModuleHeader MOD_HEADER += { + "watch-backend", + "5.0", + "backend for /watch", + "UnrealIRCd Team", + "unrealircd-5", +}; + +MOD_TEST() +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + + EfunctionAdd(modinfo->handle, EFUNC_WATCH_ADD, add_to_watch_hash_table); + EfunctionAdd(modinfo->handle, EFUNC_WATCH_DEL, del_from_watch_hash_table); + EfunctionAdd(modinfo->handle, EFUNC_WATCH_DEL_LIST, hash_del_watch_list); + EfunctionAddPVoid(modinfo->handle, EFUNC_WATCH_GET, TO_PVOIDFUNC(hash_get_watch)); + EfunctionAdd(modinfo->handle, EFUNC_WATCH_CHECK, hash_check_watch); + return MOD_SUCCESS; +} + +MOD_INIT() +{ + ModDataInfo mreq; + + MARK_AS_OFFICIAL_MODULE(modinfo); + ModuleSetOptions(modinfo->handle, MOD_OPT_PERM_RELOADABLE, 1); /* or do a complex memory freeing algorithm instead */ + + if (!watch_initialized) + { + memset(watchTable, 0, sizeof(watchTable)); + siphash_generate_key(siphashkey_watch); + watch_initialized = 1; + } + + memset(&mreq, 0 , sizeof(mreq)); + mreq.type = MODDATATYPE_LOCAL_CLIENT; + mreq.name = "watchCount", + mreq.free = dummy_free; + watchCounterMD = ModDataAdd(modinfo->handle, mreq); + if (!watchCounterMD) + { + config_error("[%s] Failed to request user watchCount moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle)); + return MOD_FAILED; + } + + memset(&mreq, 0 , sizeof(mreq)); + mreq.type = MODDATATYPE_LOCAL_CLIENT; + mreq.name = "watchList", + mreq.free = watch_free; + watchListMD = ModDataAdd(modinfo->handle, mreq); + if (!watchListMD) + { + config_error("[%s] Failed to request user watchList moddata: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle)); + return MOD_FAILED; + } + + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, watch_backend_user_quit); + + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +void dummy_free(ModData *md) +{ +} + +void watch_free(ModData *md) +{ + /* it should have been never requested to free as the module is PERM */ + if (md) + ircd_log(LOG_WARNING, "MEMORY LEAK: watchList moddata was not freed!"); +} + +int watch_backend_user_quit(Client *client, MessageTag *mtags, char *comment) +{ + /* Clean out list and watch structures -Donwulff */ + watch_del_list(client, 0); + return 0; +} + +/* + * add_to_watch_hash_table + */ +int add_to_watch_hash_table(char *nick, Client *client, int flags) +{ + unsigned int hashv; + Watch *watch; + Link *lp; + + + /* Get the right bucket... */ + hashv = hash_watch_nick_name(nick); + + /* Find the right nick (header) in the bucket, or NULL... */ + if ((watch = (Watch *)watchTable[hashv])) + while (watch && mycmp(watch->nick, nick)) + watch = watch->hnext; + + /* If found NULL (no header for this nick), make one... */ + if (!watch) { + watch = (Watch *)safe_alloc(sizeof(Watch)+strlen(nick)); + watch->lasttime = timeofday; + strcpy(watch->nick, nick); + + watch->watch = NULL; + + watch->hnext = watchTable[hashv]; + watchTable[hashv] = watch; + } + /* Is this client already on the watch-list? */ + if ((lp = watch->watch)) + while (lp && (lp->value.client != client)) + lp = lp->next; + + /* No it isn't, so add it in the bucket and client addint it */ + if (!lp) { + lp = watch->watch; + watch->watch = make_link(); + watch->watch->value.client = client; + watch->watch->flags = flags; + watch->watch->next = lp; + + lp = make_link(); + lp->next = WATCH(client); + lp->value.wptr = watch; + lp->flags = flags; + WATCH(client) = lp; + WATCHES(client)++; + } + + return 0; +} + +/* + * hash_check_watch + */ +int hash_check_watch(Client *client, int reply) +{ + unsigned int hashv; + Watch *watch; + Link *lp; + + /* Get us the right bucket */ + hashv = hash_watch_nick_name(client->name); + + /* Find the right header in this bucket */ + if ((watch = (Watch *)watchTable[hashv])) + while (watch && mycmp(watch->nick, client->name)) + watch = watch->hnext; + if (!watch) + return 0; /* This nick isn't on watch */ + + /* Update the time of last change to item */ + watch->lasttime = TStime(); + + /* Send notifies out to everybody on the list in header */ + for (lp = watch->watch; lp; lp = lp->next) + { + RunHook4(HOOKTYPE_WATCH_NOTIFICATION, client, watch, lp, reply); + } + + return 0; +} + +/* + * hash_get_watch + */ +Watch *hash_get_watch(char *nick) +{ + unsigned int hashv; + Watch *watch; + + hashv = hash_watch_nick_name(nick); + + if ((watch = (Watch *)watchTable[hashv])) + while (watch && mycmp(watch->nick, nick)) + watch = watch->hnext; + + return watch; +} + +/* + * del_from_watch_hash_table + */ +int del_from_watch_hash_table(char *nick, Client *client, int flags) +{ + unsigned int hashv; + Watch **watch, *wprev; + Link **lp, *prev; + + /* Get the bucket for this nick... */ + hashv = hash_watch_nick_name(nick); + + /* Find the right header, maintaining last-link pointer... */ + watch = (Watch **)&watchTable[hashv]; + while (*watch && mycmp((*watch)->nick, nick)) + watch = &(*watch)->hnext; + if (!*watch) + return 0; /* No such watch */ + + /* Find this client from the list of notifies... with last-ptr. */ + lp = &(*watch)->watch; + while (*lp) + { + if ((*lp)->value.client == client && ((*lp)->flags & flags) == flags) + break; + lp = &(*lp)->next; + } + if (!*lp) + return 0; /* No such client to watch */ + + /* Fix the linked list under header, then remove the watch entry */ + prev = *lp; + *lp = prev->next; + free_link(prev); + + /* Do the same regarding the links in client-record... */ + lp = (Link **)&WATCH(client); + while (*lp && ((*lp)->value.wptr != *watch)) + lp = &(*lp)->next; + + /* + * Give error on the odd case... probobly not even neccessary + * No error checking in ircd is unneccessary ;) -Cabal95 + */ + if (!*lp) + sendto_ops("WATCH debug error: del_from_watch_hash_table " + "found a watch entry with no client " + "counterpoint processing nick %s on client %p!", + nick, client->user); + else { + prev = *lp; + *lp = prev->next; + free_link(prev); + } + /* In case this header is now empty of notices, remove it */ + if (!(*watch)->watch) { + wprev = *watch; + *watch = wprev->hnext; + safe_free(wprev); + } + + /* Update count of notifies on nick */ + WATCHES(client)--; + + return 0; +} + +/* + * hash_del_watch_list + */ +int hash_del_watch_list(Client *client, int flags) +{ + unsigned int hashv; + Watch *watch; + Link **np, **lp, *prev; + + np = (Link **)&WATCH(client); + + while (*np) { + if (((*np)->flags & flags) != flags) + { + /* this entry is not fitting requested flags */ + np = &(*np)->next; + continue; + } + + WATCHES(client)--; + + /* Find the watch-record from hash-table... */ + watch = (*np)->value.wptr; + lp = &(watch->watch); + while (*lp && ((*lp)->value.client != client)) + lp = &(*lp)->next; + + /* Not found, another "worst case" debug error */ + if (!*lp) + sendto_ops("WATCH Debug error: hash_del_watch_list " + "found a WATCH entry with no table " + "counterpoint processing client %s!", + client->name); + else { + /* Fix the watch-list and remove entry */ + Link *prev = *lp; + *lp = prev->next; + free_link(prev); + + /* + * If this leaves a header without notifies, + * remove it. Need to find the last-pointer! + */ + if (!watch->watch) { + Watch **np2, *wprev; + + hashv = hash_watch_nick_name(watch->nick); + + np2 = &watchTable[hashv]; + while (*np2 && *np2 != watch) + np2 = &(*np2)->hnext; + + *np2 = watch->hnext; + + safe_free(watch); + } + } + + prev = *np; /* Save last pointer processed */ + *np = prev->next; /* Jump to the next pointer */ + free_link(prev); /* Free the previous */ + } + + if (!flags) + WATCHES(client) = 0; + + return 0; +} + +uint64_t hash_watch_nick_name(const char *name) +{ + return siphash_nocase(name, siphashkey_watch) % WATCH_HASH_TABLE_SIZE; +} + diff --git a/src/modules/watch.c b/src/modules/watch.c index f84b607cb..215f8235f 100644 --- a/src/modules/watch.c +++ b/src/modules/watch.c @@ -22,9 +22,15 @@ #include "unrealircd.h" -CMD_FUNC(cmd_watch); +#define MSG_WATCH "WATCH" -#define MSG_WATCH "WATCH" +CMD_FUNC(cmd_watch); +int watch_user_quit(Client *client, MessageTag *mtags, char *comment); +int watch_away(Client *client, MessageTag *mtags, char *reason, int already_as_away); +int watch_nickchange(Client *client, MessageTag *mtags, char *newnick); +int watch_post_nickchange(Client *client, MessageTag *mtags); +int watch_user_connect(Client *client); +int watch_notification(Client *client, Watch *watch, Link *lp, int reply); ModuleHeader MOD_HEADER = { @@ -36,9 +42,21 @@ ModuleHeader MOD_HEADER }; MOD_INIT() -{ - CommandAdd(modinfo->handle, MSG_WATCH, cmd_watch, 1, CMD_USER); +{ MARK_AS_OFFICIAL_MODULE(modinfo); + + CommandAdd(modinfo->handle, MSG_WATCH, cmd_watch, 1, CMD_USER); + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, watch_user_quit); + HookAdd(modinfo->handle, HOOKTYPE_REMOTE_QUIT, 0, watch_user_quit); + HookAdd(modinfo->handle, HOOKTYPE_AWAY, 0, watch_away); + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_NICKCHANGE, 0, watch_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_REMOTE_NICKCHANGE, 0, watch_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_POST_LOCAL_NICKCHANGE, 0, watch_post_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_POST_REMOTE_NICKCHANGE, 0, watch_post_nickchange); + HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, watch_user_connect); + HookAdd(modinfo->handle, HOOKTYPE_REMOTE_CONNECT, 0, watch_user_connect); + HookAdd(modinfo->handle, HOOKTYPE_WATCH_NOTIFICATION, 0, watch_notification); + return MOD_SUCCESS; } @@ -86,6 +104,9 @@ static void show_watch(Client *client, char *name, int rpl1, int rpl2, int awayn static char buf[BUFSIZE]; +#define WATCHES(client) (moddata_local_client(client, watchCounterMD).i) +#define WATCH(client) (moddata_local_client(client, watchListMD).ptr) + /* * cmd_watch */ @@ -109,6 +130,17 @@ CMD_FUNC(cmd_watch) parv[1] = def; } + + ModDataInfo *watchCounterMD = findmoddata_byname("watchCount", MODDATATYPE_LOCAL_CLIENT); + ModDataInfo *watchListMD = findmoddata_byname("watchList", MODDATATYPE_LOCAL_CLIENT); + + if (!watchCounterMD || !watchListMD) + { + ircd_log(LOG_WARNING, "watch: moddata not available. Check `watch-backend` module."); + sendnotice(client, "WATCH command is not available at this moment. Please try again later."); + return; + } + for (s = strtoken(&p, *++pav, " "); s; s = strtoken(&p, NULL, " ")) { if ((user = strchr(s, '!'))) @@ -127,13 +159,15 @@ CMD_FUNC(cmd_watch) continue; if (do_nick_name(s + 1)) { - if (client->local->watches >= MAXWATCH) + if (WATCHES(client) >= MAXWATCH) { sendnumeric(client, ERR_TOOMANYWATCH, s + 1); continue; } - add_to_watch_hash_table(s + 1, client, awaynotify); + watch_add(s + 1, client, + WATCH_FLAG_TYPE_WATCH | (awaynotify ? WATCH_FLAG_AWAYNOTIFY : 0) + ); } show_watch(client, s + 1, RPL_NOWON, RPL_NOWOFF, awaynotify); @@ -148,7 +182,7 @@ CMD_FUNC(cmd_watch) { if (!*(s+1)) continue; - del_from_watch_hash_table(s + 1, client); + watch_del(s + 1, client, WATCH_FLAG_TYPE_WATCH); show_watch(client, s + 1, RPL_WATCHOFF, RPL_WATCHOFF, 0); continue; @@ -160,8 +194,7 @@ CMD_FUNC(cmd_watch) */ if (*s == 'C' || *s == 'c') { - hash_del_watch_list(client); - + watch_del_list(client, WATCH_FLAG_TYPE_WATCH); continue; } @@ -173,38 +206,38 @@ CMD_FUNC(cmd_watch) if ((*s == 'S' || *s == 's') && !did_s) { Link *lp; - Watch *anptr; + Watch *watch; int count = 0; did_s = 1; /* * Send a list of how many users they have on their WATCH list - * and how many WATCH lists they are on. + * and how many WATCH lists they are on. This will also include + * other WATCH types if present - we're not checking for + * WATCH_FLAG_TYPE_*. */ - anptr = hash_get_watch(client->name); - if (anptr) - for (lp = anptr->watch, count = 1; + watch = watch_get(client->name); + if (watch) + for (lp = watch->watch, count = 1; (lp = lp->next); count++) ; - sendnumeric(client, RPL_WATCHSTAT, client->local->watches, count); + sendnumeric(client, RPL_WATCHSTAT, WATCHES(client), count); /* * Send a list of everybody in their WATCH list. Be careful * not to buffer overflow. */ - if ((lp = client->local->watch) == NULL) - { - sendnumeric(client, RPL_ENDOFWATCHLIST, *s); - continue; - } + lp = WATCH(client); *buf = '\0'; - strlcpy(buf, lp->value.wptr->nick, sizeof buf); - count = - strlen(client->name) + strlen(me.name) + 10 + - strlen(buf); - while ((lp = lp->next)) + count = strlen(client->name) + strlen(me.name) + 10; + while (lp) { + if (!(lp->flags & WATCH_FLAG_TYPE_WATCH)) + { + lp = lp->next; + continue; /* this one is not ours */ + } if (count + strlen(lp->value.wptr->nick) + 1 > BUFSIZE - 2) { @@ -215,8 +248,12 @@ CMD_FUNC(cmd_watch) strcat(buf, " "); strcat(buf, lp->value.wptr->nick); count += (strlen(lp->value.wptr->nick) + 1); + + lp = lp->next; } - sendnumeric(client, RPL_WATCHLIST, buf); + if (*buf) + /* anything to send */ + sendnumeric(client, RPL_WATCHLIST, buf); sendnumeric(client, RPL_ENDOFWATCHLIST, *s); continue; @@ -229,12 +266,17 @@ CMD_FUNC(cmd_watch) */ if ((*s == 'L' || *s == 'l') && !did_l) { - Link *lp = client->local->watch; + Link *lp = WATCH(client); did_l = 1; while (lp) { + if (!(lp->flags & WATCH_FLAG_TYPE_WATCH)) + { + lp = lp->next; + continue; /* this one is not ours */ + } if ((target = find_person(lp->value.wptr->nick, NULL))) { sendnumeric(client, RPL_NOWON, target->name, @@ -264,3 +306,86 @@ CMD_FUNC(cmd_watch) */ } } + +int watch_user_quit(Client *client, MessageTag *mtags, char *comment) +{ + if (IsUser(client)) + watch_check(client, RPL_LOGOFF); + return 0; +} + +int watch_away(Client *client, MessageTag *mtags, char *reason, int already_as_away) +{ + if (reason) + watch_check(client, already_as_away ? RPL_REAWAY : RPL_GONEAWAY); + else + watch_check(client, RPL_NOTAWAY); + + return 0; +} + +int watch_nickchange(Client *client, MessageTag *mtags, char *newnick) +{ + watch_check(client, RPL_LOGOFF); + + return 0; +} + +int watch_post_nickchange(Client *client, MessageTag *mtags) +{ + watch_check(client, RPL_LOGON); + + return 0; +} + +int watch_user_connect(Client *client) +{ + watch_check(client, RPL_LOGON); + + return 0; +} + +int watch_notification(Client *client, Watch *watch, Link *lp, int reply) +{ + int awaynotify = 0; + + if (!(lp->flags & WATCH_FLAG_TYPE_WATCH)) + return 0; + + if ((reply == RPL_GONEAWAY) || (reply == RPL_NOTAWAY) || (reply == RPL_REAWAY)) + awaynotify = 1; + + if (!awaynotify) + { + sendnumeric(lp->value.client, reply, + client->name, + (IsUser(client) ? client->user->username : ""), + (IsUser(client) ? + (IsHidden(client) ? client->user->virthost : client-> + user->realhost) : ""), watch->lasttime, client->info); + } + else + { + /* AWAY or UNAWAY */ + if (!(lp->flags & WATCH_FLAG_AWAYNOTIFY)) + return 0; /* skip away/unaway notification for users not interested in them */ + + if (reply == RPL_NOTAWAY) + sendnumeric(lp->value.client, reply, + client->name, + (IsUser(client) ? client->user->username : ""), + (IsUser(client) ? + (IsHidden(client) ? client->user->virthost : client-> + user->realhost) : ""), client->user->lastaway); + else /* RPL_GONEAWAY / RPL_REAWAY */ + sendnumeric(lp->value.client, reply, + client->name, + (IsUser(client) ? client->user->username : ""), + (IsUser(client) ? + (IsHidden(client) ? client->user->virthost : client-> + user->realhost) : ""), client->user->lastaway, client->user->away); + } + + return 0; +} + diff --git a/src/numeric.c b/src/numeric.c index 14812401b..474021c83 100644 --- a/src/numeric.c +++ b/src/numeric.c @@ -774,11 +774,11 @@ static char *replies[] = { /* 727 */ NULL, /* 728 */ NULL, /* 729 */ NULL, -/* 730 */ NULL, -/* 731 */ NULL, -/* 732 */ NULL, -/* 733 */ NULL, -/* 734 */ NULL, +/* 730 RPL_MONONLINE */ ":%s!%s@%s", +/* 731 RPL_MONOFFLINE */ ":%s", +/* 732 RPL_MONLIST */ ":%s", +/* 733 RPL_ENDOFMONLIST */ ":End of MONITOR list", +/* 734 ERR_MONLISTFULL */ "%d %s :Monitor list is full.", /* 735 */ NULL, /* 736 */ NULL, /* 737 */ NULL,