/*
 *   clones.c - Clone detection and limiting
 *   Copyright (C) 2004 Trevor Talbot and
 *                      the DALnet coding 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.
 */

/* $Id: clones.c,v 1.3 2005/07/05 03:17:53 sheik Exp $ */

/*
 * WARNING: code is chummy with throttle.c
 */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#include "blalloc.h"
#include "numeric.h"
#include "channel.h"
#include "memcount.h"

#include "throttle.h"
#include "clones.h"


#ifndef THROTTLE_ENABLE
extern BlockHeap *hashent_freelist;
#endif


static void *clones_hashtable;

BlockHeap *free_cloneents;
CloneEnt  *clones_list;
CloneStat  clones_stat;


static CloneEnt *
get_clone(char *key, int create)
{
    CloneEnt *ce;

    if (!(ce = hash_find(clones_hashtable, key)) && create)
    {
        ce = BlockHeapALLOC(free_cloneents, CloneEnt);
        memset(ce, 0, sizeof(*ce));
        strcpy(ce->ent, key);
        hash_insert(clones_hashtable, ce);
        ce->next = clones_list;
        if (clones_list)
            clones_list->prev = ce;
        clones_list = ce;
    }

    return ce;
}

static void
expire_clone(CloneEnt *ce)
{
    if (ce->gcount || ce->limit || ce->sllimit || ce->sglimit)
        return;

    if (ce->next)
        ce->next->prev = ce->prev;
    if (ce->prev)
        ce->prev->next = ce->next;
    else
        clones_list = ce->next;
    hash_delete(clones_hashtable, ce);
    BlockHeapFree(free_cloneents, ce);
}


#ifdef THROTTLE_ENABLE
static void
get_clones(aClient *cptr, CloneEnt **ceip, CloneEnt **ce24, int create)
{
    char ip24[HOSTIPLEN+1];
    char *s;

    strcpy(ip24, cptr->hostip);
    /* deliberate core if strrchr fails -- we need a valid IP string */
    s = strrchr(ip24, '.');
    *++s = '*';
    *++s = 0;

    *ceip = get_clone(cptr->hostip, create);
    *ce24 = get_clone(ip24, create);
}

static int
report_lclone(aClient *cptr, CloneEnt *ce, int l, int is24, char *t, char *n)
{
    if (n)
        sendto_realops_lev(REJ_LEV, "clone %s!%s@%s (%s %d/%d local %s %s)",
                           cptr->name, cptr->user->username, cptr->user->host,
                           ce->ent, ce->lcount, l, t, n);
    else
        sendto_realops_lev(REJ_LEV, "clone %s!%s@%s (%s %d/%d local %s)",
                           cptr->name, cptr->user->username, cptr->user->host,
                           ce->ent, ce->lcount, l, t);

    if (is24)
        clones_stat.rls++;
    else
        clones_stat.rlh++;

    throttle_force(cptr->hostip);

    return (is24 ? 2 : 1);
}

static int
report_gclone(aClient *cptr, CloneEnt *ce, int l, int is24, char *t)
{
    sendto_realops_lev(REJ_LEV, "clone %s!%s@%s (%s %d/%d global %s)",
                       cptr->name, cptr->user->username, cptr->user->host,
                       ce->ent, ce->gcount, l, t);
    
    if (is24)
        clones_stat.rgs++;
    else
        clones_stat.rgh++;

    throttle_force(cptr->hostip);

    return (is24 ? 2 : 1);
}

/*
 * Checks a local client against the clone limits.
 * Returns 1 if IP/32 limit hit, 2 if IP/24 limit hit, 0 otherwise.
 */
int
clones_check(aClient *cptr)
{
    CloneEnt *ceip;
    CloneEnt *ce24;
    int       limit;
    int       lpri = 0;
    int       gpri = 0;

    get_clones(cptr, &ceip, &ce24, 0);

    if (ceip)
    {
        /* local limit priority stack: soft set, class, default */
        if ((limit = ceip->sllimit))
        {
            lpri = 3;
            if (ceip->lcount >= limit)
                return report_lclone(cptr, ceip, limit, 0, "soft", NULL);
        }
        else if ((limit = cptr->user->allow->class->connfreq))
        {
            lpri = 2;
            if (ceip->lcount >= limit)
                return report_lclone(cptr, ceip, limit, 0, "class",
                                     cptr->user->allow->class->name);
        }
        else
        {
            lpri = 1;
            limit = local_ip_limit;
            if (ceip->lcount >= limit)
                return report_lclone(cptr, ceip, limit, 0, "default", NULL);
        }

        /* global limit priority stack: soft set, services, default */
        if ((limit = ceip->sglimit))
        {
            gpri = 3;
            if (ceip->gcount >= limit)
                return report_gclone(cptr, ceip, limit, 0, "soft");
        }
        else if ((limit = ceip->limit))
        {
            gpri = 2;
            if (ceip->gcount >= limit)
                return report_gclone(cptr, ceip, limit, 0, "hard");
        }
        else
        {
            gpri = 1;
            limit = global_ip_limit;
            if (ceip->gcount >= limit)
                return report_gclone(cptr, ceip, limit, 0, "default");
        }
    }

    if (ce24)
    {
        /* For local limits, a specific host limit provides an implicit
         * exemption from site limits of a lower priority. */
        if ((limit = ce24->sllimit))
        {
            if (ce24->lcount >= limit)
                return report_lclone(cptr, ce24, limit, 1, "soft", NULL);
        }
        else if (lpri <= 2 && (limit = cptr->user->allow->class->ip24clones))
        {
            if (ce24->lcount >= limit)
                return report_lclone(cptr, ce24, limit, 1, "class",
                                     cptr->user->allow->class->name);
        }
        else if (lpri <= 1)
        {
            limit = local_ip24_limit;
            if (ce24->lcount >= limit)
                return report_lclone(cptr, ce24, limit, 1, "default", NULL);
        }

        /* For global limits, the implicit exemption is for the default only;
         * the soft limit can only be lower, not higher, so the service-set
         * hard limit wins if it's not present. */
        if ((limit = ce24->sglimit))
        {
            if (ce24->gcount >= limit)
                return report_gclone(cptr, ce24, limit, 1, "soft");
        }
        else if ((limit = ce24->limit))
        {
            if (ce24->gcount >= limit)
                return report_gclone(cptr, ce24, limit, 1, "hard");
        }
        else if (gpri <= 1)
        {
            limit = global_ip24_limit;
            if (ce24->gcount >= limit)
                return report_gclone(cptr, ce24, limit, 1, "default");
        }
    }
    
    return 0;
}

/*
 * Adds a client to the clone list.
 */
void
clones_add(aClient *cptr)
{
    CloneEnt *ceip;
    CloneEnt *ce24;

    get_clones(cptr, &ceip, &ce24, 1);

    cptr->clone.prev = NULL;
    cptr->clone.next = ceip->clients;
    if (ceip->clients)
        ceip->clients->clone.prev = cptr;
    ceip->clients = cptr;

    ceip->gcount++;
    ce24->gcount++;

    if (MyConnect(cptr))
    {
        ceip->lcount++;
        ce24->lcount++;
    }
}

/*
 * Removes a client from the clone list.
 */
void
clones_remove(aClient *cptr)
{
    CloneEnt *ceip;
    CloneEnt *ce24;

    get_clones(cptr, &ceip, &ce24, 0);

    if (cptr->clone.next)
        cptr->clone.next->clone.prev = cptr->clone.prev;
    if (cptr->clone.prev)
        cptr->clone.prev->clone.next = cptr->clone.next;
    else
        ceip->clients = cptr->clone.next;

    ceip->gcount--;
    ce24->gcount--;

    /* !$%#&*%@ user state handling! */
    if (cptr->uplink == &me)
    {
        ceip->lcount--;
        ce24->lcount--;
    }

    expire_clone(ceip);
    expire_clone(ce24);
}
#endif  /* THROTTLE_ENABLE */

/*
 * Sets a global clone limit.  A limit of 0 reverts to default settings.
 * Returns -1 on invalid parameters, old value otherwise.
 */
int
clones_set(char *ent, int type, int limit)
{
    CloneEnt *ce;
    int       rval = 0;

    if (strlen(ent) > HOSTIPLEN)
        return -1;

    if (limit < 0)
        return -1;

    ce = get_clone(ent, 1);

    switch (type)
    {
        case CLIM_HARD_GLOBAL:
            rval = ce->limit;
            ce->limit = limit;
            if (limit && ce->sglimit > limit)
                ce->sglimit = 0;
            break;

        case CLIM_SOFT_GLOBAL:
            rval = ce->sglimit;
            ce->sglimit = limit;
            break;

        case CLIM_SOFT_LOCAL:
            rval = ce->sllimit;
            ce->sllimit = limit;
            break;
    }

    expire_clone(ce);

    return rval;
}

/*
 * Gets the current clone limits.  0 means using default.
 */
void clones_get(char *ent, int *hglimit, int *sglimit, int *sllimit)
{
    CloneEnt *ce;

    ce = get_clone(ent, 0);

    if (ce)
    {
        *hglimit = ce->limit;
        *sglimit = ce->sglimit;
        *sllimit = ce->sllimit;
    }
    else
    {
        *hglimit = 0;
        *sglimit = 0;
        *sllimit = 0;
    }
}

/*
 * Propagate global clone limits.
 */
void
clones_send(aClient *cptr)
{
    CloneEnt *ce;

    for (ce = clones_list; ce; ce = ce->next)
    {
        if (!ce->limit)
            continue;
        sendto_one(cptr, ":%s SVSCLONE %s %d", me.name, ce->ent, ce->limit);
    }
}

/*
 * Must be called AFTER throttle_init()
 */
void
clones_init(void)
{
#ifndef THROTTLE_ENABLE
    hashent_freelist = BlockHeapCreate(sizeof(hashent), 1024);
#endif
    free_cloneents = BlockHeapCreate(sizeof(CloneEnt), 1024);
    clones_hashtable = create_hash_table(THROTTLE_HASHSIZE,
                                         offsetof(CloneEnt, ent), HOSTIPLEN,
                                         2, (void *)strcmp);
}

u_long
memcount_clones(MCclones *mc)
{
    CloneEnt *ce;

    mc->file = __FILE__;

    for (ce = clones_list; ce; ce = ce->next)
        mc->e_cloneents++;

    mc->e_heap = free_cloneents;
    mc->e_hash = clones_hashtable;

    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1