/* contrib/m_force.c
 * Copyright (C) 2002, 2003, 2004, 2005 Hybrid Development Team
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are
 *  met:
 *
 *  1.Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  2.Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  3.The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 *  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: m_force.c 690 2006-06-17 11:34:25Z michael $
 */

#include "stdinc.h"
#include "handlers.h"
#include "client.h"
#include "common.h"     /* FALSE bleah */
#include "ircd.h"
#include "irc_string.h"
#include "numeric.h"
#include "fdlist.h"
#include "hash.h"
#include "s_bsd.h"
#include "s_conf.h"
#include "s_serv.h"
#include "send.h"
#include "msg.h"
#include "parse.h"
#include "modules.h"
#include "channel.h"
#include "channel_mode.h"

static void mo_forcejoin(struct Client *, struct Client *, int parc, char *[]);
static void mo_forcepart(struct Client *, struct Client *, int parc, char *[]);

struct Message forcejoin_msgtab = {
  "FORCEJOIN", 0, 0, 3, 0, MFLG_SLOW, 0,
  { m_ignore, m_not_oper, mo_forcejoin, mo_forcejoin, mo_forcejoin, m_ignore }
};

struct Message forcepart_msgtab = {
  "FORCEPART", 0, 0, 3, 0, MFLG_SLOW, 0,
  { m_ignore, m_not_oper, mo_forcepart, mo_forcepart, mo_forcepart, m_ignore }
};

#ifndef STATIC_MODULES
void
_modinit(void)
{
  mod_add_cmd(&forcejoin_msgtab);
  mod_add_cmd(&forcepart_msgtab);
}

void
_moddeinit(void)
{
  mod_del_cmd(&forcejoin_msgtab);
  mod_del_cmd(&forcepart_msgtab);
}

const char *_version = "$Revision: 690 $";
#endif

/* m_forcejoin()
 *  parv[0] = sender prefix
 *  parv[1] = user to force
 *  parv[2] = channel to force them into
 */
static void
mo_forcejoin(struct Client *client_p, struct Client *source_p,
             int parc, char *parv[])
{
  struct Client *target_p = NULL;
  struct Channel *chptr = NULL;
  unsigned int type = 0;
  char mode = '\0';
  char sjmode = '\0';
  char *newch = NULL;
  dlink_node *ptr = NULL;

  if (!IsAdmin(source_p))
  {
    sendto_one(source_p, form_str(ERR_NOPRIVILEGES),
               me.name, source_p->name);
    return;
  }

  if ((target_p = find_client(parv[1])) == NULL || !IsClient(target_p))
  {
    sendto_one(source_p, form_str(ERR_NOSUCHNICK),
               me.name, source_p->name, parv[1]);
    return;
  }

  if (!MyConnect(target_p))
  {
    if (target_p->from != client_p)
    {
      if (IsCapable(target_p->from, CAP_ENCAP))
        sendto_one(target_p, ":%s ENCAP %s FORCEJOIN %s %s",
                   source_p->name, target_p->from->name,
                   target_p->name, parv[2]);
      else
        sendto_one(target_p, ":%s FORCEJOIN %s %s",
                   source_p->name, target_p->name, parv[2]);
    }

    return;
  }

  /* select our modes from parv[2] if they exist... (chanop)*/
  switch (*parv[2])
  {
    case '@':
      type = CHFL_CHANOP;
      mode = 'o';
      sjmode = '@';
      parv[2]++;
      break;
#ifdef HALFOPS
    case '%':
      type = CHFL_HALFOP;
      mode = 'h';
      sjmode = '%';
      parv[2]++;
      break;
#endif
    case '+':
      type = CHFL_VOICE;
      mode = 'v';
      sjmode = '+';
      parv[2]++;
      break;
    default:
      type = 0;
      mode = sjmode = '\0'; /* make sure sjmode is 0. sjoin depends on it */
      break;
  }

  if ((chptr = hash_find_channel(parv[2])) != NULL)
  {
    if (IsMember(target_p, chptr))
    {
      sendto_one(source_p, ":%s NOTICE %s :*** Notice -- %s is already in %s",
                 me.name, source_p->name, target_p->name, chptr->chname);
      return;
    }

    add_user_to_channel(chptr, target_p, type, NO);

    sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s!%s@%s JOIN :%s",
                         target_p->name, target_p->username,
                         target_p->host, chptr->chname);

    if (sjmode)
      sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s MODE %s +%c %s",
                           me.name, chptr->chname, mode, target_p->name);

    if (chptr->chname[0] == '#')
    {
      if (sjmode)
      {
        DLINK_FOREACH (ptr, serv_list.head)
        {
          struct Client *serv_p = ptr->data;
          if (serv_p == target_p->from || IsDead(serv_p))
            continue;

          sendto_one(serv_p, ":%s SJOIN %lu %s + :%c%s",
                     ID_or_name(&me, serv_p), (unsigned long)chptr->channelts,
                     chptr->chname, (sjmode == '%' &&
                     !IsCapable(serv_p, CAP_HOPS)) ? '@' : sjmode,
                     ID_or_name(target_p, serv_p));
        }
      }
      else
      {
        sendto_server(target_p, target_p, chptr, CAP_TS6, NOCAPS, LL_ICLIENT,
                      ":%s SJOIN %lu %s + :%s",
                      me.id, (unsigned long)chptr->channelts,
                      chptr->chname, target_p->id);
        sendto_server(target_p, target_p, chptr, NOCAPS, CAP_TS6, LL_ICLIENT,
                      ":%s SJOIN %lu %s + :%s",
                      me.name, (unsigned long)chptr->channelts,
                      chptr->chname, target_p->name);
      }
    }

    if (chptr->topic != NULL)
    {
      sendto_one(target_p, form_str(RPL_TOPIC),
                 me.name, target_p->name,
                 chptr->chname, chptr->topic);
      sendto_one(target_p, form_str(RPL_TOPICWHOTIME),
                 me.name, target_p->name, chptr->chname,
                 chptr->topic_info, chptr->topic_time);
    }

    target_p->localClient->last_join_time = CurrentTime;
    channel_member_names(target_p, chptr, 1);
  }
  else
  {
    newch = parv[2];

    if (!check_channel_name(newch, 1))
    {
      sendto_one(source_p, form_str(ERR_BADCHANNAME),
                 me.name, source_p->name, newch);
      return;
    }

    /*
     * it would be interesting here to allow an oper
     * to force target_p into a channel that doesn't exist
     * even more so, into a local channel when we disable
     * local channels... but...
     * I don't want to break anything - scuzzy
     */
    if (ConfigChannel.disable_local_channels && (*newch == '&'))
    {
      sendto_one(source_p, form_str(ERR_NOSUCHCHANNEL),
                 me.name, source_p->name, newch);
      return;
    }

    chptr = make_channel(newch);
    add_user_to_channel(chptr, target_p, CHFL_CHANOP, NO);

    /* send out a join, make target_p join chptr */
    if (chptr->chname[0] == '#')
    {
      sendto_server(target_p, target_p, chptr, CAP_TS6, NOCAPS, LL_ICLIENT,
                    ":%s SJOIN %lu %s +nt :@%s",
                    me.id, (unsigned long)chptr->channelts,
                    chptr->chname, ID(target_p));
      sendto_server(target_p, target_p, chptr, NOCAPS, CAP_TS6, LL_ICLIENT,
                    ":%s SJOIN %lu %s +nt :@%s",
                    me.name, (unsigned long)chptr->channelts,
                    chptr->chname, target_p->name);
    }

    sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s!%s@%s JOIN :%s",
                         target_p->name, target_p->username,
                         target_p->host, chptr->chname);

    chptr->mode.mode |= MODE_TOPICLIMIT | MODE_NOPRIVMSGS;

    sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s MODE %s +nt",
                         me.name, chptr->chname);

    target_p->localClient->last_join_time = CurrentTime;
    channel_member_names(target_p, chptr, 1);

    /* we do this to let the oper know that a channel was created, this will be
     * seen from the server handling the command instead of the server that
     * the oper is on.
     */
    sendto_one(source_p, ":%s NOTICE %s :*** Notice -- Creating channel %s",
               me.name, source_p->name, chptr->chname);
  }
}

static void
mo_forcepart(struct Client *client_p, struct Client *source_p,
             int parc, char *parv[])
{
  struct Client *target_p = NULL;
  struct Channel *chptr = NULL;
  struct Membership *member = NULL;

  if (!IsAdmin(source_p))
  {
    sendto_one(source_p, form_str(ERR_NOPRIVILEGES),
               me.name, source_p->name);
    return;
  }

  /* if target_p == NULL then let the oper know */
  if ((target_p = find_client(parv[1])) == NULL || !IsClient(target_p))
  {
    sendto_one(source_p, form_str(ERR_NOSUCHNICK),
               me.name, source_p->name, parv[1]);
    return;
  }

  if (!MyConnect(target_p))
  {
    if (target_p->from != client_p)
    {
      if (IsCapable(target_p->from, CAP_ENCAP))
        sendto_one(target_p, ":%s ENCAP %s FORCEPART %s %s",
                   source_p->name, target_p->from->name,
                   target_p->name, parv[2]);
      else
        sendto_one(target_p, ":%s FORCEPART %s %s",
                   source_p->name, target_p->name, parv[2]);
    }

    return;
  }

  if ((chptr = hash_find_channel(parv[2])) == NULL)
  {
    sendto_one(source_p, form_str(ERR_NOSUCHCHANNEL),
               me.name, source_p->name, parv[2]);
    return;
  }

  if ((member = find_channel_link(target_p, chptr)) == NULL)
  {
    sendto_one(source_p, form_str(ERR_USERNOTINCHANNEL),
               me.name, source_p->name, chptr->chname, target_p->name);
    return;
  }

  if (chptr->chname[0] == '#')
  {
    sendto_server(target_p, target_p, chptr, CAP_TS6, NOCAPS, LL_ICLIENT,
                  ":%s PART %s :%s", ID(target_p),
                  chptr->chname, target_p->name);
    sendto_server(target_p, target_p, chptr, NOCAPS, CAP_TS6, LL_ICLIENT,
                  ":%s PART %s :%s", target_p->name,
                  chptr->chname, target_p->name);
  }

  sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s!%s@%s PART %s :%s",
                       target_p->name, target_p->username,
                       target_p->host, chptr->chname,
                       target_p->name);
  remove_user_from_channel(member);
}
