/************************************************************************
 *   IRC - Internet Relay Chat, src/modules.c
 *   Copyright (C) 2003, Lucas Madar
 */

/* $Id: modules.c,v 1.6 2005/07/09 03:09:12 sheik Exp $ */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include "throttle.h"
#include "h.h"
#include "hooks.h"
#include "memcount.h"

extern Conf_Modules *modules;

#ifndef USE_HOOKMODULES
int 
m_module(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    if(MyClient(sptr) && !IsAnOper(sptr))
    {
        sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
        return 0;
    }

    if (parc > 2)
    {
        if (!mycmp(parv[1], "LIST") || !mycmp(parv[1], "HOOKS"))
        {
            if (hunt_server(cptr, sptr, "%s MODULE %s %s", 2, parc, parv)
                != HUNTED_ISME)
                return 0;
        }
        else if (!mycmp(parv[1], "CGLOBAL"))
        {
            char pbuf[512];

            if(!(IsServer(sptr) || IsULine(sptr)))
                return 0;

            /* Pass this on to all servers! */
            make_parv_copy(pbuf, parc, parv);
            sendto_serv_butone(cptr, ":%s MODULE %s", parv[0], pbuf);

            return 0;
        }
    }

    if (IsPerson(sptr))
        sendto_one(sptr, "%s NOTICE %s :I don't have module support.",
                   me.name, sptr->name);

    return 0;
}

int 
call_hooks(enum c_hooktype hooktype, ...)
{
    return 0;
}

int 
init_modules()
{
    return 0;
}

#else

#include <dlfcn.h>

/* XXX hack.  check on RTLD_NOW later. */
#ifndef RTLD_NOW
#define RTLD_NOW 0
#endif

DLink *module_list = NULL;

typedef struct loaded_module 
{
    char *name;

    char *version;
    char *description;

    void *handle;

    void (*module_check) (int *);
    int  (*module_init) (void *);
    void (*module_shutdown) (void);
    void (*module_getinfo) (char **, char **);
    int  (*module_command) (aClient *, int, char **);
    int  (*module_globalcommand) (aClient *, aClient *, int, char **);
} aModule;

/* Forward decls */
char *bircmodule_strdup(char *);
void *bircmodule_malloc(int);
void  bircmodule_free(void *);
void  drop_all_hooks(aModule *owner);
void  list_hooks(aClient *sptr);

aModule *
find_module(char *name) 
{
    DLink *lp;

    for(lp = module_list; lp; lp = lp->next)
    {
        aModule *mod = (aModule *) lp->value.cp;

        if(strcmp(mod->name, name) == 0)
            return mod;
    }
    return NULL;
}

aModule *
find_module_opaque(void *opaque) 
{
    DLink *lp;

    for(lp = module_list; lp; lp = lp->next)
    {
        aModule *mod = (aModule *) lp->value.cp;

        if(opaque == (void *) mod)
            return mod;
    }
    return NULL;
}

int 
modsym_load(aClient *sptr, char *modname, char *symbol, void *modulehandle, 
            void **retfunc)
{
    void *ret;
    const char *error;

    ret = dlsym(modulehandle, symbol);

    if((error = dlerror()) != NULL)
    {
        if(sptr)
            sendto_one(sptr, ":%s NOTICE %s :Module symbol error for %s/%s: %s",
                       me.name, sptr->name, modname, symbol, error);
        else
            fprintf(stderr, " - Module symbol error for %s/%s: %s\n",
                    modname, symbol, error);
        
        dlclose(modulehandle);
        return 0;
    }

    *retfunc = ret;
    return 1;
}

void 
list_modules(aClient *sptr)
{
    DLink *lp;

    for(lp = module_list; lp; lp = lp->next)
    {
        aModule *mod = (aModule *) lp->value.cp;
        sendto_one(sptr, ":%s NOTICE %s :Module: %s    Version: %s",
                   me.name, sptr->name, mod->name, mod->version);

        sendto_one(sptr, ":%s NOTICE %s :  - %s", me.name, sptr->name, 
                   mod->description);
    }
}

void 
destroy_module(aModule *themod)
{
    (*themod->module_shutdown)();
    dlclose(themod->handle);
    bircmodule_free(themod->name);
    bircmodule_free(themod->version);
    bircmodule_free(themod->description);   
    remove_from_list(&module_list, themod, NULL);
    bircmodule_free(themod);
}

int 
load_module(aClient *sptr, char *modname)
{
    aModule tmpmod, *themod;
    char mnamebuf[512], *ver, *desc;
    int acsz = -1, ret;

    if((themod = find_module(modname)))
    {
        if(sptr)
            sendto_one(sptr, ":%s NOTICE %s :Module %s is already loaded"
                       " [version: %s]", me.name, sptr->name, modname, 
                       themod->version);
        else
            fprintf(stderr, " - Module %s is already loaded [version: %s]\n",
                    modname, themod->version);
        return 0;
    }

    if(modules && modules->module_path)
        ircsnprintf(mnamebuf, 512, "%s/%s.so", modules->module_path, modname);
    else
        ircsnprintf(mnamebuf, 512, "%s/modules/%s.so", dpath, modname);

    tmpmod.handle = dlopen(mnamebuf, RTLD_NOW);
    if(tmpmod.handle == NULL)
    {
        if(sptr)
            sendto_one(sptr, ":%s NOTICE %s :Module load error for %s: %s",
                       me.name, sptr->name, modname, dlerror());
        else
            fprintf(stderr, " - Module load error for %s: %s\n",
                    modname, dlerror());
        return -1;
    }

    if(!modsym_load(sptr, modname, "bircmodule_check", tmpmod.handle, 
                    (void **) &tmpmod.module_check))
        return -1;
    if(!modsym_load(sptr, modname, "bircmodule_init", tmpmod.handle, 
                    (void **) &tmpmod.module_init))
        return -1;
    if(!modsym_load(sptr, modname, "bircmodule_shutdown", tmpmod.handle, 
                    (void **) &tmpmod.module_shutdown))
        return -1;
    if(!modsym_load(sptr, modname, "bircmodule_getinfo", tmpmod.handle, 
                    (void **) &tmpmod.module_getinfo))
        return -1;
    if(!modsym_load(sptr, modname, "bircmodule_command", tmpmod.handle, 
                    (void **) &tmpmod.module_command))
        return -1;
    if(!modsym_load(sptr, modname, "bircmodule_globalcommand", tmpmod.handle, 
                    (void **) &tmpmod.module_globalcommand))
        return -1;

    (*tmpmod.module_check)(&acsz);
    if(acsz != MODULE_INTERFACE_VERSION)
    {
        if(sptr)
            sendto_one(sptr, ":%s NOTICE %s :Module load error for %s:"
                    " Incompatible module (My interface version: %d Module"
                    " version: %d)", me.name, sptr->name, modname, 
                    MODULE_INTERFACE_VERSION, acsz);
        else
            fprintf(stderr, " - Module load error for %s: Incompatible module ("
                            "My interface version: %d Module version: %d)\n",
                    modname, MODULE_INTERFACE_VERSION, acsz);
        dlclose(tmpmod.handle);
        return -1;
    }

    tmpmod.name = bircmodule_strdup(modname);

    ver = desc = NULL;
    (*tmpmod.module_getinfo)(&ver, &desc);
    tmpmod.version = bircmodule_strdup((ver != NULL) ? ver : "<no version>");
    tmpmod.description = bircmodule_strdup((desc != NULL) ? desc : 
                                                           "<no description>");
    themod = (aModule *) bircmodule_malloc(sizeof(aModule));
    memcpy(themod, &tmpmod, sizeof(aModule));
    add_to_list(&module_list, themod);

    ret = (*themod->module_init)((void *) themod);

    if(ret == 0)
    {
        if(sptr)
            sendto_one(sptr, ":%s NOTICE %s :Module %s successfully loaded"
                       " [version: %s]", me.name, sptr->name, modname, 
                       themod->version);
        else
            fprintf(stderr, " - Module %s successfully loaded [version: %s]\n",
                    modname, themod->version);

        call_hooks(MHOOK_LOAD, modname, (void *) themod);
    }
    else
    {
        drop_all_hooks(themod);
        destroy_module(themod);

        if(sptr)
            sendto_one(sptr, ":%s NOTICE %s :Module %s load failed (module"
                        " requested unload)", me.name, sptr->name, modname);
        else
            fprintf(stderr, " - Module %s load failed (module requested"
                            " unload)\n", modname);
    }
    return 0;
}

int 
unload_module(aClient *sptr, char *modname)
{
    aModule *themod = find_module(modname);

    if(!themod)
    {
        sendto_one(sptr, ":%s NOTICE %s :Module %s is not loaded",
                   me.name, sptr->name, modname);
        return 0;
    }

    drop_all_hooks(themod);
    call_hooks(MHOOK_UNLOAD, themod->name, (void *) themod);
    destroy_module(themod);

    sendto_one(sptr, ":%s NOTICE %s :Module %s successfully unloaded",
               me.name, sptr->name, modname);

    return 0;
}

int 
m_module(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    if(!IsAnOper(sptr))
    {
        sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
        return 0;
    }

    if(parc < 2)
    {
        sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name,
                        parv[0], "MODULE");
        return 0;
    }

    /* this should technically never happen anyway, but.. */
    if(!MyClient(sptr) && !(IsAnOper(sptr) || IsULine(sptr) || IsServer(sptr)))
        return 0;

    if(mycmp(parv[1], "LOAD") == 0)
    {
        if(!(MyClient(sptr) && IsAdmin(sptr)))
        {
            sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
            return 0;
        }
        if(!BadPtr(parv[2]))
            load_module(sptr, parv[2]);
        else
        {
            sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name,
                       parv[0], "MODULE");
            return 0;
        }

    }
    else if(mycmp(parv[1], "UNLOAD") == 0)
    {
        if(!(MyClient(sptr) && IsAdmin(sptr)))
        {
            sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
            return 0;
        }
        if(!BadPtr(parv[2]))
            unload_module(sptr, parv[2]);
        else
        {
            sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name,
                       parv[0], "MODULE");
            return 0;
        }
    }
    else if(mycmp(parv[1], "LIST") == 0)
    {
        if(parc > 2 && hunt_server(cptr, sptr, ":%s MODULE %s %s", 2,
                       parc, parv) != HUNTED_ISME)
            return 0;

        list_modules(sptr);
        sendto_one(sptr, ":%s NOTICE %s :--- End of module list ---",
                   me.name, sptr->name);
    }
    else if(mycmp(parv[1], "HOOKS") == 0)
    {
        if(parc > 2 && hunt_server(cptr, sptr, ":%s MODULE %s %s", 2,
                                   parc, parv) != HUNTED_ISME)
            return 0;

        list_hooks(sptr);
        sendto_one(sptr, ":%s NOTICE %s :--- End of hook list ---",
                   me.name, sptr->name);
    }
    else if(mycmp(parv[1], "CMD") == 0)
    {
        aModule *themod;
        if(!(MyClient(sptr) && IsAdmin(sptr)))
        {
            sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
            return 0;
        }
        if(BadPtr(parv[2]))
        {
            sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name,
                       parv[0], "MODULE");
            return 0;
        }
        themod = find_module(parv[2]);
        if(!themod)
        {
            sendto_one(sptr, ":%s NOTICE %s :Module %s not found for cmd",
                       me.name, sptr->name, parv[2]);
            return 0;
        }
        return (*themod->module_command) (sptr, parc - 2, parv + 2);
    }
    else if(parc > 2 && mycmp(parv[1], "CGLOBAL") == 0)
    {
        char pbuf[512];
        aModule *themod;

        if(!(IsServer(sptr) || IsULine(sptr)))
            return 0;

        themod = find_module(parv[2]);

        /* Pass this on to all servers! */
        make_parv_copy(pbuf, parc, parv);
        sendto_serv_butone(cptr, ":%s MODULE %s", parv[0], pbuf);

        if(themod)
            return (*themod->module_globalcommand) 
                                            (cptr, sptr, parc - 2, parv + 2);
    }
    return 0;
}


/* module memory functions */

char *
bircmodule_strdup(char *string)
{
    char *ret = MyMalloc(strlen(string) + 1);
    strcpy(ret, string);
    return ret;
}

void *
bircmodule_malloc(int size)
{
    return MyMalloc(size);
}

void 
bircmodule_free(void *p)
{
    MyFree(p);
}

/* hook functions */

typedef struct module_hook 
{
    aModule *owner;
    void *funcptr;
    int hooktype;
} aHook;

static DLink *preaccess_hooks = NULL;
static DLink *postaccess_hooks = NULL;
static DLink *postmotd_hooks = NULL;
static DLink *msg_hooks = NULL;
static DLink *chanmsg_hooks = NULL;
static DLink *usermsg_hooks = NULL;
static DLink *mymsg_hooks = NULL;
static DLink *every10_hooks = NULL;
static DLink *signoff_hooks = NULL;
static DLink *mload_hooks = NULL;
static DLink *munload_hooks = NULL;

static DLink *all_hooks = NULL;

char *
get_texthooktype(enum c_hooktype hooktype)
{
    static char ubuf[32];

    switch(hooktype)
    {
        case CHOOK_10SEC:
            return "10 seconds";

        case CHOOK_PREACCESS:
            return "Pre-access";

        case CHOOK_POSTACCESS:
            return "Post-access";

        case CHOOK_POSTMOTD:
            return "Post-MOTD";

        case CHOOK_MSG:
            return "Message";

        case CHOOK_CHANMSG:
            return "Channel Message";

        case CHOOK_USERMSG:
            return "User targeted Message";

        case CHOOK_MYMSG:
            return "Message to me";

        case CHOOK_SIGNOFF:
            return "Signoff";

        case MHOOK_LOAD:
            return "Module load";

        case MHOOK_UNLOAD:
            return "Module unload";

        default:
            ircsnprintf(ubuf, 32, "Unknown (%d)", hooktype);
            return ubuf;
    }
}

DLink **
get_hooklist(enum c_hooktype hooktype)
{
    DLink **hooklist;

    switch(hooktype)
    {
        case CHOOK_10SEC:
            hooklist = &every10_hooks;
            break;

        case CHOOK_PREACCESS:
            hooklist = &preaccess_hooks;
            break;

        case CHOOK_POSTACCESS:
            hooklist = &postaccess_hooks;
            break;

        case CHOOK_POSTMOTD:
            hooklist = &postmotd_hooks;
            break;

        case CHOOK_MSG:
            hooklist = &msg_hooks;
            break;

        case CHOOK_CHANMSG:
            hooklist = &chanmsg_hooks;
            break;

        case CHOOK_USERMSG:
            hooklist = &usermsg_hooks;
            break;

        case CHOOK_MYMSG:
            hooklist = &mymsg_hooks;
            break;

        case CHOOK_SIGNOFF:
            hooklist = &signoff_hooks;
            break;

        case MHOOK_LOAD:
            hooklist = &mload_hooks;
            break;

        case MHOOK_UNLOAD:
            hooklist = &munload_hooks;
            break;

        default:
            return NULL;
    }
    return hooklist;
}

void 
drop_all_hooks(aModule *owner)
{
    DLink *lp, *lpn, **hooklist;

    for(lp = all_hooks; lp; lp = lpn)
    {
        aHook *hk = (aHook *) lp->value.cp;

        lpn = lp->next;

        if(hk->owner == owner)
        {
            sendto_realops_lev(DEBUG_LEV, "Module cleanup: removing hook [%s]"
                            " for opaque %d", get_texthooktype(hk->hooktype), 
                            (int) owner);

            hooklist = get_hooklist((enum c_hooktype) hk->hooktype);

            remove_from_list(hooklist, hk, NULL);
            remove_from_list(&all_hooks, hk, NULL);
            bircmodule_free(hk);
        }
    }
}

void *
bircmodule_add_hook(enum c_hooktype hooktype, void *opaque, void *funcptr)
{
    DLink **hooklist;
    aHook *hk;
    aModule *owner;

    if(!(owner = find_module_opaque(opaque)))
    {
        sendto_realops_lev(DEBUG_LEV, "Module tried to add hooktype %d with"
                         " unknown opaque 0x%x", (int) hooktype, (int) opaque);
        return NULL;
    }

    if((hooklist = get_hooklist(hooktype)) == NULL)
        return NULL;

    hk = (aHook *) bircmodule_malloc(sizeof(aHook));
    hk->owner = owner;
    hk->funcptr = funcptr;
    hk->hooktype = (int) hooktype;

    add_to_list(&all_hooks, hk);
    add_to_list(hooklist, hk);

    return (void *) hk;
}

void 
bircmodule_del_hook(void *opaque)
{
    DLink *lp, *lpn, **hooklist;

    for(lp = all_hooks; lp; lp = lpn)
    {
        aHook *hk = (aHook *) lp->value.cp;

        lpn = lp->next;

        if((void *) hk == opaque)
        {
            hooklist = get_hooklist((enum c_hooktype) hk->hooktype);

            remove_from_list(hooklist, hk, NULL);
            remove_from_list(&all_hooks, hk, NULL);
            bircmodule_free(hk);
        }
    }
}

int 
call_hooks(enum c_hooktype hooktype, ...)
{
    va_list vl;
    int ret = 0;
    DLink *lp;

    va_start(vl, hooktype);

    switch(hooktype)
    {
        case CHOOK_10SEC:
            for(lp = every10_hooks; lp; lp = lp->next)
            {
                void (*rfunc) () = ((aHook *)lp->value.cp)->funcptr;
                (*rfunc)();
            }
            break;

        case CHOOK_PREACCESS:
            {
                aClient *acptr = va_arg(vl, aClient *);

                for(lp = preaccess_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (aClient *) = ((aHook *)lp->value.cp)->funcptr;
                    if((ret = (*rfunc)(acptr)) == FLUSH_BUFFER)
                        break;
                }
                break;
            }

        case CHOOK_POSTACCESS:
            {
                aClient *acptr = va_arg(vl, aClient *);

                for(lp = postaccess_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (aClient *) = ((aHook *)lp->value.cp)->funcptr;
                    if((ret = (*rfunc)(acptr)) == FLUSH_BUFFER)
                        break;
                }
                break;
            }

        case CHOOK_POSTMOTD:
            {
                aClient *acptr = va_arg(vl, aClient *);

                for(lp = postmotd_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (aClient *) = ((aHook *)lp->value.cp)->funcptr;
                    if((ret = (*rfunc)(acptr)) == FLUSH_BUFFER)
                        break;
                }
                break;
            }

        case CHOOK_MSG:
            {
                aClient *acptr = va_arg(vl, aClient *);
                int aint = va_arg(vl, int);
                char *txtptr = va_arg(vl, char *);

                for(lp = msg_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (aClient *, int, char *) = 
                                    ((aHook *)lp->value.cp)->funcptr;
                    if((ret = (*rfunc)(acptr, aint, txtptr)) == FLUSH_BUFFER)
                        break;
                }
                break;
            }

        case CHOOK_CHANMSG:
            {
                aClient *acptr = va_arg(vl, aClient *);
                aChannel *chptr = va_arg(vl, aChannel *);
                int aint = va_arg(vl, int);
                char *txtptr = va_arg(vl, char *);

                for(lp = chanmsg_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (aClient *, aChannel *, int, char *) =
                                  ((aHook *)lp->value.cp)->funcptr;
                    if((ret = (*rfunc)(acptr, chptr, aint, txtptr)) 
                                    == FLUSH_BUFFER)
                        break;
                }
                break;
            }

        case CHOOK_USERMSG:
            {
                aClient *acptr = va_arg(vl, aClient *);
                aClient *dcptr = va_arg(vl, aClient *);
                int aint = va_arg(vl, int);
                char *txtptr = va_arg(vl, char *);

                for(lp = usermsg_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (aClient *, aClient *, int, char *) =
                                 ((aHook *)lp->value.cp)->funcptr;
                    if((ret = (*rfunc)(acptr, dcptr, aint, txtptr))
                                    == FLUSH_BUFFER)
                        break;
                }
                break;
            }

        case CHOOK_MYMSG:
            {
                aClient *acptr = va_arg(vl, aClient *);
                int aint = va_arg(vl, int);
                char *txtptr = va_arg(vl, char *);
    
                for(lp = mymsg_hooks; lp; lp = lp->next)
                {  
                    int (*rfunc) (aClient *, int, char *) = 
                                 ((aHook *)lp->value.cp)->funcptr;
                    if((ret = (*rfunc)(acptr, aint, txtptr)) == FLUSH_BUFFER)
                        break;
                }
                break;
            }

        case CHOOK_SIGNOFF:
            {
                aClient *acptr = va_arg(vl, aClient *);
                for(lp = signoff_hooks; lp; lp = lp->next)
                {
                    void (*rfunc) (aClient *) = 
                                    ((aHook *)lp->value.cp)->funcptr;
                    (*rfunc)(acptr);
                }
                break;
            }

        case MHOOK_LOAD:
            {
                char *txtptr = va_arg(vl, char *);
                void *avoid = va_arg(vl, void *);

                for(lp = mload_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (char *, void *) = 
                                ((aHook *)lp->value.cp)->funcptr;
                    (*rfunc)(txtptr, avoid);
                }
                break;
            }

        case MHOOK_UNLOAD:
            {
                char *txtptr = va_arg(vl, char *);
                void *avoid = va_arg(vl, void *);
                for(lp = munload_hooks; lp; lp = lp->next)
                {
                    int (*rfunc) (char *, void *) = 
                                  ((aHook *)lp->value.cp)->funcptr;
                    (*rfunc)(txtptr, avoid);
                }
                break;
            }
      
        default:
            sendto_realops_lev(DEBUG_LEV, "Call for unknown hook type %d", 
                hooktype);
            break;
    }   
    va_end(vl);
    return ret;
}

void 
list_hooks(aClient *sptr)
{
    DLink *lp;

    for(lp = all_hooks; lp; lp = lp->next)
    {
        aHook *hook = (aHook *) lp->value.cp;
        aModule *mod = hook->owner;

        sendto_one(sptr, ":%s NOTICE %s :Module: %s  Type: %s",
                   me.name, sptr->name, mod->name, 
                   get_texthooktype(hook->hooktype));
    }
}

int init_modules()
{
    int i;

    if(!modules)
        return 0;

    for(i = 0; modules->autoload[i]; i++)
    {
        load_module(NULL, modules->autoload[i]);
        printf("Module %s Loaded Successfully.\n", modules->autoload[i]);
    }
    return 0;
}
#endif

u_long
memcount_modules(MCmodules *mc)
{
#ifdef USE_HOOKMODULES
    int      c;
    DLink   *dl;
    aModule *m;
#endif

    mc->file = __FILE__;

#ifdef USE_HOOKMODULES
    for (dl = module_list; dl; dl = dl->next)
    {
        mc->e_dlinks++;
        m = (aModule *)dl->value.cp;
        mc->modules.c++;
        mc->modules.m += sizeof(*m);
        if (m->name)
            mc->modules.m += strlen(m->name) + 1;
        if (m->version)
            mc->modules.m += strlen(m->version) + 1;
        if (m->description)
            mc->modules.m += strlen(m->description) + 1;
    }

    c = mc_dlinks(all_hooks);
    mc->hooks.c = c;
    mc->hooks.m = c * sizeof(aHook);
    mc->e_dlinks += c;

    mc->e_dlinks += mc_dlinks(preaccess_hooks);
    mc->e_dlinks += mc_dlinks(postaccess_hooks);
    mc->e_dlinks += mc_dlinks(postmotd_hooks);
    mc->e_dlinks += mc_dlinks(msg_hooks);
    mc->e_dlinks += mc_dlinks(chanmsg_hooks);
    mc->e_dlinks += mc_dlinks(usermsg_hooks);
    mc->e_dlinks += mc_dlinks(mymsg_hooks);
    mc->e_dlinks += mc_dlinks(every10_hooks);
    mc->e_dlinks += mc_dlinks(signoff_hooks);
    mc->e_dlinks += mc_dlinks(mload_hooks);
    mc->e_dlinks += mc_dlinks(munload_hooks);

    mc->total.c += mc->modules.c + mc->hooks.c;
    mc->total.m += mc->modules.m + mc->hooks.m;

    return mc->total.m;
#else
    return 0;
#endif
}



syntax highlighted by Code2HTML, v. 0.9.1