/* * 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 = NULL; static char *siphashkey_watch = NULL; void dummy_free(ModData *md); void watch_free(ModData *md); int watch_backend_user_quit(Client *client, MessageTag *mtags, const char *comment); int _watch_add(char *nick, Client *client, int flags); int _watch_check(Client *client, int event, void *data, int (*watch_notify)(Client *client, Watch *watch, Link *lp, int event, void *data)); Watch *_watch_get(char *nick); int _watch_del(char *nick, Client *client, int flags); int _watch_del_list(Client *client, int flags); uint64_t hash_watch_nick_name(const char *name); ModuleHeader MOD_HEADER = { "watch-backend", "6.0.3", "backend for /WATCH", "UnrealIRCd Team", "unrealircd-6", }; MOD_TEST() { MARK_AS_OFFICIAL_MODULE(modinfo); EfunctionAdd(modinfo->handle, EFUNC_WATCH_ADD, _watch_add); EfunctionAdd(modinfo->handle, EFUNC_WATCH_DEL, _watch_del); EfunctionAdd(modinfo->handle, EFUNC_WATCH_DEL_LIST, _watch_del_list); EfunctionAddPVoid(modinfo->handle, EFUNC_WATCH_GET, TO_PVOIDFUNC(_watch_get)); EfunctionAdd(modinfo->handle, EFUNC_WATCH_CHECK, _watch_check); return MOD_SUCCESS; } void watch_generic_free(ModData *m) { safe_free(m->ptr); } 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 */ LoadPersistentPointer(modinfo, siphashkey_watch, watch_generic_free); if (siphashkey_watch == NULL) { siphashkey_watch = safe_alloc(SIPHASH_KEY_LENGTH); siphash_generate_key(siphashkey_watch); } LoadPersistentPointer(modinfo, watchTable, watch_generic_free); if (watchTable == NULL) watchTable = safe_alloc(sizeof(Watch *) * WATCH_HASH_TABLE_SIZE); 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() { SavePersistentPointer(modinfo, siphashkey_watch); SavePersistentPointer(modinfo, watchTable); 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) { unreal_log(ULOG_ERROR, "watch-backend", "BUG_WATCH_FREE_MEMORY_LEAK", NULL, "[BUG] watchList moddata was not freed -- memory leak!"); } } int watch_backend_user_quit(Client *client, MessageTag *mtags, const char *comment) { /* Clean out list and watch structures -Donwulff */ watch_del_list(client, 0); return 0; } int _watch_add(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 = watchTable[hashv])) while (watch && mycmp(watch->nick, nick)) watch = watch->hnext; /* If found NULL (no header for this nick), make one... */ if (!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)++; RunHook(HOOKTYPE_WATCH_ADD, nick, client, flags); } return 0; } int _watch_check(Client *client, int event, void *data, int (*watch_notify)(Client *client, Watch *watch, Link *lp, int event, void *data)) { 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 = 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) { watch_notify(client, watch, lp, event, data); } return 0; } Watch *_watch_get(char *nick) { unsigned int hashv; Watch *watch; hashv = hash_watch_nick_name(nick); if ((watch = watchTable[hashv])) while (watch && mycmp(watch->nick, nick)) watch = watch->hnext; return watch; } int _watch_del(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) { unreal_log(ULOG_WARNING, "watch", "BUG_WATCH_DEL", client, "[BUG] watch_del found a watch entry with no client counterpoint, " "while processing nick $nick on client $client.details", log_data_string("nick", nick)); } 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)--; RunHook(HOOKTYPE_WATCH_DEL, nick, client, flags); return 0; } int _watch_del_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) { unreal_log(ULOG_WARNING, "watch", "BUG_WATCH_DEL_LIST", client, "[BUG] watch_del_list found a watch entry with no table counterpoint, " "while processing client $client.details"); } 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; }