/**********************************************************************
 * PTlink IRC Services is (C) CopyRight PTlink IRC Software 1999-2006 *
 *                     http://software.pt-link.net                    *
 * This program is distributed under GNU Public License               *
 * Please read the file COPYING for copyright information.            *
 **********************************************************************
                                                                                
  Description: chanserv role command
                                                                                
*/

#include "module.h"

#include "chanrecord.h"
#include "chanserv.h"
#include "my_sql.h"
#define CS_ROLE
#include "cs_role.h"
#include "nsmacros.h"
#include "nickserv.h"
#include "dbconf.h"
/* lang files */
#include "lang/common.lh"
#include "lang/cscommon.lh"
#include "lang/cs_role.lh"

SVS_Module mod_info =
/* module, version, description */
{"cs_role", "4.7", "chanserv role functions" };

/* Change Log
  4.7 - #64: Fixed SecureOps & BotServ
  4.6 - #63: validate_options() provided by strhand.c
  4.5 - #54: CS ROLE DEL ALL
        #26: Added chanserv suspensions
  4.4 - #27 : half-op support
  4.3 - #30 : users can drop the admin role
        #29 : possible sql injection on cs_role
        #16 : autoop isn't working when joing a channel  
  4.2 - 0000339: attempt to change the admin role crashes services
        0000334: review code to use local bot when possible
        0000321: HelpChan option to set +h when ops join the help chan
  4.1 - 0000322: cs_role crash when upgrading from ptsvs2 database
  4.0 - 0000305: foreign keys for data integrity
          Added cs_role.3.sql changes          
  3.0 - 0000284: irc opers should not be kicked from restricted/forbidden chans
      - 0000283: chanserv forbidden has no effect
      - 0000281: No auth nicks can't use chanserv
      - 0000265: remove nickserv cache system
  2.1 - we don't need irc_lower()
      - call mod_abort_event() when the user is kicked
      - added P_AKICK
  2.0 - added support for accept/reject roles
  1.1 - fixed the message for MaxChanUsers, was using the wrong value
*/
  

#define DB_VERSION	3

#define DEF_ADMIN_ACTIONS     A_AADM | A_AOP | A_MSG
#define DEF_ADMIN_PERMS       P_SET | P_KICK | P_OPDEOP | P_LIST | P_VIEW | P_VOICEDEVOICE | P_INVITE | P_UNBAN | P_CLEAR | P_AKICK | P_HOPDEHOP

#define DEF_OPERATOR_ACTIONS	A_AOP | A_MSG
#ifdef HALFOPS
#define DEF_OPERATOR_PERMS	P_KICK | P_OPDEOP | P_LIST | P_VOICEDEVOICE | P_INVITE | P_UNBAN | P_HOPDEHOP
#else
#define DEF_OPERATOR_PERMS	P_KICK | P_OPDEOP | P_LIST | P_VOICEDEVOICE | P_INVITE | P_UNBAN
#endif

#ifdef HALFOPS
#define DEF_HALFOPERATOR_ACTIONS	A_AHOP | A_MSG
#define DEF_HALFOPERATOR_PERMS		P_KICK | P_VOICEDEVOICE | P_INVITE | P_UNBAN | P_HOPDEHOP
#endif

#define DEF_VOICE_ACTIONS	A_AVOICE | A_MSG
#define DEF_VOICE_PERMS		P_VOICEDEVOICE

/* external functions we need */
ServiceUser* (*chanserv_suser)(void);
int e_chan_register;
int e_regchan_join;
int e_nick_identify;

MOD_REQUIRES
  MOD_FUNC(dbconf_get)
  MOD_FUNC(dbconf_get_or_build)
  MOD_FUNC(chanserv_suser)
  MOD_FUNC(e_chan_register)
  MOD_FUNC(e_regchan_join)
  MOD_FUNC(e_nick_identify)
MOD_END

/** functions/events we provide **/
/* int role_with_permission(u_int32_t scid, u_int32_t snid, int permission); */

MOD_PROVIDES
  MOD_FUNC(role_with_permission)
MOD_END

/* Internal functions declaration */
u_int32_t find_role(u_int32_t scid, char *rname);
u_int32_t create_role(u_int32_t scid, char *rname, u_int32_t mroleid, u_int32_t actions, u_int32_t permissions);
int drop_role(u_int32_t roleid, u_int32_t scid);
int add_to_role(u_int32_t roleid, u_int32_t scid, u_int32_t snid, u_int32_t who, char* msg, int flags);
int del_from_role(u_int32_t scid, u_int32_t snid);
int del_roles_from(u_int32_t scid);
int roles_count(u_int32_t scid);
int users_count(u_int32_t scid);
int is_member_or_master(u_int32_t snid, u_int32_t rid);
int role_is_master(u_int32_t roleid, u_int32_t masterid);
int is_master(u_int32_t snid, u_int32_t rid);
void set_role_master(u_int32_t roleid, u_int32_t masterid);
void set_role_prop(u_int32_t roleid, u_int32_t actions, u_int32_t perms);
void fix_channels_roles(void);
void ev_cs_role_nick_identify(IRC_User* u, u_int32_t* snid);
void ev_cs_role_timer_part(IRC_Chan* chan, int tag);
void ev_cs_role_op(IRC_Chan *chan, IRC_User *user);
int sql_upgrade(int version, int post);

/* core event handlers */
int ev_cs_chan_register(IRC_User *u, ChanRecord* cr);
int ev_cs_role_chan_join(ChanRecord *cr, IRC_ChanNode* cn);

/* available commands from module */
void cs_role(IRC_User *s, IRC_User *u);

/* Local config */
static int MaxRolesPerChan;
static int MaxUsersPerChan;
static int RoleAcceptance;
static char *HelpChan;

DBCONF_PROVIDES
  DBCONF_INT(MaxRolesPerChan, "10", 
    "Max. number of roles for each channel")
  DBCONF_INT(MaxUsersPerChan, "100",
    "Max. number of users with assigned roles for each channel")
  DBCONF_SWITCH(RoleAcceptance, "on",
    "Users need to accept the roles they get assigned to")
  DBCONF_STR_OPT(HelpChan, NULL,
    "Users will get user mode +h when joining this chan")
DBCONF_END

/* Remote config */
static int NeedsAuth;

DBCONF_REQUIRES
  DBCONF_GET("chanserv", NeedsAuth)
DBCONF_END

int mod_rehash(void)
{
  if(dbconf_get(dbconf_requires) < 0 )
  {
    errlog("Error reading dbconf!");
    return -1;
  }
  if(dbconf_get_or_build(mod_info.name, dbconf_provides) < 0 )
  {
    errlog("Error reading dbconf!");
    return -1;
  }
  return 0;
}

/* role actions */
#define A_AADM		0x00000001
#define A_AOP			0x00000002
#define A_AVOICE	0x00000004
/* #define A_AKICK		0x00000008 reuse this later */
#define A_NOTICE	0x00000010
#define A_MSG		0x00000020
#define A_AHOP		0x00000040

/* actions declaration here */
int action_aadm(IRC_Chan *chan, IRC_ChanNode* cn, char* msg);
int action_aop(IRC_Chan *chan, IRC_ChanNode* cn, char* msg);
int action_ahop(IRC_Chan *chan, IRC_ChanNode* cn, char* msg);
int action_avoice(IRC_Chan *chan, IRC_ChanNode* cn, char* msg);
int action_notice(IRC_Chan *chan, IRC_ChanNode* cn, char* msg);
int action_msg(IRC_Chan *chan, IRC_ChanNode* cn, char* msg);

OptionMask actions_mask[] = 
  {
    { "aadm", A_AADM, action_aadm },
    { "aop", A_AOP, action_aop },
    { "ahop", A_AHOP, action_ahop },
    { "avoice", A_AVOICE, action_avoice },
    { "notice", A_NOTICE, action_notice },
    { "msg", A_MSG, action_msg },
    { NULL }
};

OptionMask permissions_mask[] =
  {
    { "set", P_SET, NULL },
    { "kick", P_KICK, NULL },
    { "opdeop", P_OPDEOP, NULL },
    { "hopdehop", P_HOPDEHOP, NULL },
    { "voice", P_VOICEDEVOICE, NULL },    
    { "list", P_LIST, NULL },
    { "view", P_VIEW, NULL },
    { "invite", P_INVITE, NULL  },    
    { "unban", P_UNBAN, NULL  },
    { "clear", P_CLEAR, NULL },        
    { "akick", P_AKICK, NULL },
    { NULL }
};
        

/* Local variables */
ServiceUser* csu;
int cs_log;


int mod_load(void)
{
  int r;
  cs_log = log_handle("chanserv");
  
  r = sql_check_inst_upgrade(mod_info.name, DB_VERSION, sql_upgrade);

  if( r < 0 )
    return -4;
  else if(r == 1) /* table was installed */
    fix_channels_roles(); /* this is needed for svs2 tables */
  
  /* */
  csu = chanserv_suser();  
  
  suser_add_cmd(csu, "ROLE", cs_role, ROLE_SUMMARY, ROLE_HELP);
  suser_add_help(csu, "ACTIONLIST", CHAN_ROLE_ACTIONLIST);
  suser_add_help(csu, "PERMLIST", CHAN_ROLE_PERMLIST);
  suser_add_help(csu, "ROLE CREATE", CHAN_ROLE_CREATE_HELP);
  suser_add_help(csu, "ROLE DROP", CHAN_ROLE_DROP_HELP);
  suser_add_help(csu, "ROLE VIEW", CHAN_ROLE_VIEW_HELP);
  suser_add_help(csu, "ROLE LIST", CHAN_ROLE_LIST_HELP);
  suser_add_help(csu, "ROLE ADD", CHAN_ROLE_ADD_HELP);
  suser_add_help(csu, "ROLE DEL", CHAN_ROLE_DEL_HELP);  
  suser_add_help(csu, "ROLE SETMSG", CHAN_ROLE_SETMSG_HELP);
  suser_add_help(csu, "ROLE SET", CHAN_ROLE_SET_HELP);
  suser_add_help(csu, "ROLE ACCEPT", CHAN_ROLE_ACCEPT_HELP);
  suser_add_help(csu, "ROLE REJECT", CHAN_ROLE_REJECT_HELP);

  /* Add actions  */    
  mod_add_event_action(e_chan_register, (ActionHandler) ev_cs_chan_register);
  mod_add_event_action(e_regchan_join, (ActionHandler) ev_cs_role_chan_join);
  mod_add_event_action(e_nick_identify, (ActionHandler) ev_cs_role_nick_identify);
  
  /* we take care of secureops */
  irc_AddCmodeChange("+o", ev_cs_role_op);
  
  return 0;
}

void
mod_unload(void)
{
  suser_del_mod_cmds(csu, &mod_info);
}

/* core events implementation */
int ev_cs_chan_register(IRC_User *u, ChanRecord* cr)
{
  u_int32_t founder_rid;
  u_int32_t operator_rid;
#ifdef HALFOPS  
  u_int32_t halfoperator_rid;
#endif  
  u_int32_t  voice_rid;
  int r;
  
  founder_rid = create_role(cr->scid, "admin", 0, DEF_ADMIN_ACTIONS, DEF_ADMIN_PERMS);
  if(founder_rid == 0)
    {
      send_lang(u, csu->u, CHAN_ROLE_CREATE_ERROR_X_X, "admin", cr->name);
      return 0;
    }
   send_lang(u, csu->u, CHAN_ROLE_X_X_CREATED, "admin", cr->name);
  
  r = add_to_role(founder_rid, cr->scid, u->snid, u->snid, NULL, 0);
  if(r>0)
    send_lang(u, csu->u, NICK_X_ADDED_TO_ROLE_X_ON_X,
      u->nick, "admin", cr->name);

  operator_rid = create_role(cr->scid, "operator", founder_rid, DEF_OPERATOR_ACTIONS, DEF_OPERATOR_PERMS);
  if(operator_rid == 0)
    {
      send_lang(u, csu->u, CHAN_ROLE_CREATE_ERROR_X_X, "operator", cr->name);
      return 0;
    }
  /* send_lang(u, csu->u, CHAN_ROLE_X_X_CREATED, "operator", cr->name);  */
#ifdef HALFOPS  
  halfoperator_rid = create_role(cr->scid, "halfoperator", operator_rid, DEF_HALFOPERATOR_ACTIONS, DEF_HALFOPERATOR_PERMS);  
  if(halfoperator_rid == 0)
    {
      send_lang(u, csu->u, CHAN_ROLE_CREATE_ERROR_X_X, "halfoperator", cr->name);
      return 0;
    }
  /* send_lang(u, csu->u, CHAN_ROLE_X_X_CREATED, "halfoperator", cr->name);  */
  voice_rid = create_role(cr->scid, "voice", halfoperator_rid, DEF_VOICE_ACTIONS, DEF_VOICE_PERMS);
#else
  voice_rid = create_role(cr->scid, "voice", operator_rid, DEF_VOICE_ACTIONS, DEF_VOICE_PERMS);
#endif  
  if(voice_rid == 0)
    {
      send_lang(u, csu->u, CHAN_ROLE_CREATE_ERROR_X_X, "voice", cr->name);
      return 0;
    }
  /* send_lang(u, csu->u, CHAN_ROLE_X_X_CREATED, "voice", cr->name); */
  
  return 0;
}

/* TO DO */
/* internal functions implementation */
u_int32_t create_role(u_int32_t scid, char *rname, u_int32_t mroleid, u_int32_t act, u_int32_t priv)
{  

  if(mroleid)
    return sql_execute("INSERT INTO cs_role VALUES(0, %d, %s, %d, %d ,%d)",
	scid, sql_str(rname), mroleid, act, priv);
  else
    return sql_execute("INSERT INTO cs_role VALUES(0, %d, %s, NULL, %d ,%d)",
  	scid, sql_str(rname), act, priv);
}

int drop_role(u_int32_t roleid, u_int32_t scid)
{
  MYSQL_RES* res;
  MYSQL_ROW row;
  u_int32_t master_rid = 0;
  
  res = sql_query("SELECT rid FROM cs_role WHERE scid=%d and master_rid IS NULL", scid);
  if((row = sql_next_row(res)) && row[0])
    master_rid = atoi(row[0]);
  sql_free(res);
  if(master_rid == 0)
    {
      slog(L_ERROR, "Attempt to drop masterless role %d", roleid);
      return 0;
    }
 
  /* First remaster any roles for which the dropped role is master 
  to the founder role */ 
  sql_execute("UPDATE cs_role SET master_rid=%d WHERE master_rid = %d",
    master_rid, roleid);
    
  /* Now delete the data */
  return sql_execute("DELETE FROM cs_role WHERE rid=%d", roleid);
}


/* s = service the command was sent to
   u = user the command was sent from */
void cs_role(IRC_User *s, IRC_User *u)
{
  u_int32_t source_snid;
  ChanRecord* cr = NULL;
  char *valres;
  u_int32_t snid;
  char *chname,*cmd;
  
  chname = strtok(NULL, " ");  
  cmd = strtok(NULL, " ");

  /* status validation */
  CHECK_IF_IDENTIFIED_NICK  
  
  /* base syntax validation */
  if(NeedsAuth && !IsAuthenticated(u))
    send_lang(u, s, NEEDS_AUTH_NICK);
  else
  if(IsNull(chname) || IsNull(cmd))
    send_lang(u, s, CHAN_ROLE_SYNTAX);    
  else if((cr = OpenCR(chname)) == NULL)
    send_lang(u, s, CHAN_X_NOT_REGISTERED, chname);
  else if(strcasecmp(cmd, "CREATE") == 0) /* */
    {
      char *rname; /* role name */
      char *mrole; /* master role */        
      u_int32_t mroleid;
      u_int32_t iactions = 0;
      u_int32_t iperms = 0;
      char *actions;
      char *perms;
          
      rname = strtok(NULL, " ");
      mrole = strtok(NULL, " ");
      actions = strtok(NULL,":");
      perms = strtok(NULL," ");
      
      /* syntax validation */
      if( IsNull(rname) || IsNull(mrole) )
        send_lang(u, s, CHAN_ROLE_CREATE_SYNTAX);
      /* permissions validation */
      else if((source_snid != cr->founder)) /* it's not founder */
        send_lang(u,s, ONLY_FOUNDER_X, chname);
      /* check requirements */
      else if((mroleid = find_role(cr->scid, mrole)) == 0)
        send_lang(u, s, CHAN_ROLE_X_X_NOT_FOUND, mrole, chname);
      else if(actions && ((valres = validate_options(actions, actions_mask, &iactions)) != NULL))
        send_lang(u, s, INVALID_ACTION_X, valres);
      else if(perms && ((valres = validate_options(perms, permissions_mask, &iperms)) != NULL))
        send_lang(u, s, INVALID_PRIVILEGE_X, valres);
      else if(MaxRolesPerChan && roles_count(cr->scid) >= MaxRolesPerChan)
        send_lang(u, s, REACHED_MAX_ROLES_X, MaxRolesPerChan);
      /* avoid duplicates */        
      else if(find_role(cr->scid, rname) != 0)
        send_lang(u, s, CHAN_ROLE_X_X_ALREADY_EXISTS, rname, chname);        
      /* execute operation */
      else if(create_role(cr->scid, rname, mroleid, iactions, iperms) > 0)
      /* report operation status */
        send_lang(u, s, CHAN_ROLE_X_X_CREATED, rname, chname);
      else
        send_lang(u, s, CHAN_ROLE_CREATE_ERROR_X_X, rname, chname);
    }
  else if(strcasecmp(cmd, "DROP") == 0) /* drop role */
    {
      char *rname;
      u_int32_t roleid;
          
      rname = strtok(NULL, " ");
      /* syntax validation */
      if(IsNull(rname))
        send_lang(u, s, CHAN_ROLE_DROP_SYNTAX);
      /* privileges validation */
      if((source_snid != cr->founder)) /* it's not founder */
        send_lang(u,s, ONLY_FOUNDER_X, chname);        
      /* check requirements */
      else 
      if((roleid = find_role(cr->scid, rname)) == 0)
        send_lang(u, s, CHAN_ROLE_X_X_NOT_FOUND, rname, chname);            
      else 
      /* NOTE !!! Here we assume find_role gets the master on field 1 */
      if(sql_field(1) == NULL)
        send_lang(u, s, CANT_DROP_ADMIN_ROLE_ON_X, chname);
      /* execute operation */
      else if(drop_role(roleid, cr->scid)>0)
      /* report operation status */
        send_lang(u, s, CHAN_ROLE_X_X_DROP, rname, chname);
      else      
        send_lang(u, s, UPDATE_FAIL);
    }
  else if(strcasecmp(cmd, "ADD") == 0) /* add role nick message */
    {
      char *rname;
      char *rnick;
      char *msg;
      u_int32_t roleid;

      rname = strtok(NULL, " ");
      rnick = strtok(NULL, " "); 
      msg = strtok(NULL,"");
      /* syntax validation */
      if(IsNull(rname) || IsNull(rnick))
        send_lang(u, s, CHAN_ROLE_ADD_SYNTAX);
      /* check requirements */
      else if((snid = nick2snid(rnick)) == 0)
        send_lang(u, s, NICK_X_NOT_REGISTERED, rnick);
      else if((roleid = find_role(cr->scid, rname)) == 0 )
        send_lang(u, s, CHAN_ROLE_X_X_NOT_FOUND, rname, chname);
      /* privileges validation */
      else if((source_snid != cr->founder) /* its not founder */
            && !is_master(source_snid, roleid)) /* its not master */
        send_lang(u,s, NOT_MASTER_OF_X_ON_X, rname, chname);
      else if(sql_singlequery("SELECT r.rid, r.name FROM cs_role r, cs_role_users u"
            " WHERE u.scid=%d AND u.snid=%d AND r.rid=u.rid", 
            cr->scid, snid) != 0)
        send_lang(u, s, NICK_X_IS_X_ON_X, rnick, sql_field(1), cr->name);
      else if(MaxUsersPerChan && users_count(cr->scid) >= MaxUsersPerChan)
        send_lang(u, s, REACHED_MAX_USERS_X, MaxUsersPerChan);
      else 
        {
          int flags;
          if(RoleAcceptance && (source_snid != snid))
            flags = CRF_PENDING;
          else
            flags = 0;
          if(add_to_role(roleid, cr->scid, snid, source_snid, msg, flags) != 0)
            {
              send_lang(u, s, NICK_X_ADDED_TO_ROLE_X_ON_X,
                rnick, rname, cr->name);
              if(flags & CRF_PENDING)
                {
                  IRC_User* target;
                  target = irc_FindUser(rnick);
                  send_lang(u, s, ROLE_PENDING);                  
                  if(target && target->snid) /* online and identified */
                    send_lang(target, s, ROLE_X_X_X_X_PENDING,
                      rname, cr->name, cr->name, cr->name);
                }
            
            }
        }
    }
  else if(strcasecmp(cmd, "DEL") == 0) /* add role nick message */
    {
      char *rnick;      
      int is_all = 0;
      rnick = strtok(NULL, " ");
      if(rnick && (strcasecmp(rnick, "all") == 0))
        is_all = 1;
      if(IsNull(rnick))
        send_lang(u, s, CHAN_ROLE_DEL_SYNTAX);
      else if(!is_all && (snid = nick2snid(rnick)) == 0)
        send_lang(u, s, NICK_X_NOT_REGISTERED, rnick);
      else if(!is_all && sql_singlequery("SELECT r.rid, r.name FROM cs_role r, cs_role_users u"
            " WHERE u.scid=%d AND u.snid=%d AND r.rid=u.rid",
            cr->scid, snid) == 0)
        send_lang(u, s, NICK_X_HAS_NO_ROLE_ON_X, rnick, chname);
      else
        {
          if(is_all)
          {
            if(source_snid != cr->founder) /* its not founder */
              send_lang(u,s, CHAN_NOT_FOUNDER_X, chname);
            else
            {
              send_lang(u,s, DELETED_ALL_ROLES_X, chname);
              del_roles_from(cr->scid);
            }
          }
          else
          {            
            u_int32_t roleid = atoi(sql_field(0));
            char *is_rname = strdup(sql_field(1));

            if((source_snid != cr->founder) /* its not founder */
              && !is_master(source_snid, roleid) /* its not master */
              && (source_snid != snid)) /* is not the role owner */
                send_lang(u,s, NOT_MASTER_OF_NICK_X_ON_X, rnick, cr->name);
            else if(del_from_role(cr->scid, snid) != 0)
              send_lang(u, s, NICK_X_DELETED_FROM_ROLE_X_ON_X,
                rnick, is_rname, cr->name);
            free(is_rname);
          }
        }        
    }
  else if(strcasecmp(cmd,"LIST") == 0) /* List all users with assigned roles */
    {
      if(source_snid != cr->founder && 
        !irc_IsUMode(u, UMODE_OPER) &&
        role_with_permission(cr->scid, source_snid, P_LIST) == 0)
        send_lang(u, s, NO_LIST_PERM_ON_X, chname);
      else
        {          
          u_int32_t roleid;
          char *rname;
          char sqlcmd[256];
          char sqlcmd2[64];
          
          rname = strtok(NULL, " ");
          if(rname && ((roleid = find_role(cr->scid, rname)) == 0 ))
            send_lang(u, s, CHAN_ROLE_X_X_NOT_FOUND, rname, chname);
          else        
            {           
              MYSQL_RES* res; 
              snprintf(sqlcmd, sizeof(sqlcmd), 
                "SELECT n.nick, cr.name, cu.message, cu.flags "
                "FROM cs_role_users cu,nickserv n, cs_role cr "
                "WHERE cu.scid=%d AND cr.rid=cu.rid AND n.snid=cu.snid", 
                  cr->scid); 
              if(rname)
                {
                  snprintf(sqlcmd2,sizeof(sqlcmd2), " AND cr.rid=%d", roleid);
                  strcat(sqlcmd, sqlcmd2);
                }                
              send_lang(u, s, ROLE_LIST_HEADER_X, cr->name);
              res = sql_query("%s", sqlcmd);
              while(sql_next_row(res))
                {
                  char flagstr[64];
                  char* msg;
                  int flags;
                  msg = sql_field(2);
                  flags = atoi(sql_field(3));                  
                  flagstr[0] = '\0';                  
                  if(flags & CRF_REJECTED)
                    strncat(flagstr, lang_str(u, REJECTED_ROLE), 63);
                  else if(flags & CRF_PENDING)
                    strncat(flagstr, lang_str(u, PENDING_ROLE), 63);
                  if(msg)
                    send_lang(u, s, ROLE_LIST_X_X_X_X,
                      sql_field(0), sql_field(1), flagstr, msg);
                  else
                    send_lang(u, s, ROLE_LIST_X_X_X,
                     sql_field(0), sql_field(1), flagstr, msg);
                }
              send_lang(u, s, ROLE_LIST_TAIL);                
              sql_free(res);
            }
        }
    }    
  else if(strcasecmp(cmd,"VIEW") == 0) /* List all roles */
    {
      if(source_snid != cr->founder && 
      !irc_IsUMode(u, UMODE_OPER) &&
      role_with_permission(cr->scid, source_snid, P_VIEW) == 0)
        send_lang(u, s, NO_VIEW_PERM_ON_X, chname);
      else
        { 
          MYSQL_RES *res;         
          send_lang(u, s, ROLE_VIEW_HEADER_X, cr->name);          
          
          /* firs list all masterless roles on a different query
           this should catch at least founder*/
          res = sql_query("SELECT cr.name, cr.master_rid, cr.actions, cr.perms FROM cs_role cr"
            " WHERE cr.scid=%d AND cr.master_rid IS NULL", cr->scid);
          while(sql_next_row(res))
            {
              char *actionstr;
              char *permstr;

              actionstr = strdup(mask_string(actions_mask, atoi(sql_field(2))));
              permstr = strdup(mask_string(permissions_mask, atoi(sql_field(3))));              
              send_lang(u, s, ROLE_VIEW_X_X_X_X, sql_field(0), "", actionstr, permstr);
              free(actionstr);free(permstr);            
            }
          sql_free(res);
          /* now get all the other roles */
          res = sql_query("SELECT cr.name, cr.master_rid, cr.actions, cr.perms, mr.name FROM cs_role cr, cs_role mr"
            " WHERE cr.scid=%d AND mr.rid=cr.master_rid", cr->scid);
          while(sql_next_row(res))
            {
              char *actionstr;
              char *permstr;
              
              actionstr = strdup(mask_string(actions_mask, atoi(sql_field(2))));
              permstr = strdup(mask_string(permissions_mask, atoi(sql_field(3))));
              send_lang(u, s, ROLE_VIEW_X_X_X_X, sql_field(0), sql_field(4), actionstr, permstr);
              free(actionstr);free(permstr);
            }
          send_lang(u, s, ROLE_VIEW_TAIL);
          sql_free(res);
        }
    }
  else if(strcasecmp(cmd,"SETMSG") == 0) /* Sets the role message */
    {
      char *rnick, *rmsg;
      
      rnick = strtok(NULL, " ");
      rmsg = strtok(NULL, "");
      if(IsNull(rnick))
        send_lang(u, s, CHAN_ROLE_SETMSG_SYNTAX);
      else if((snid = nick2snid(rnick)) == 0)
        send_lang(u, s, NICK_X_NOT_REGISTERED, rnick);
      else if(sql_singlequery("SELECT r.rid, r.name FROM cs_role r, cs_role_users u"
              " WHERE u.scid=%d AND u.snid=%d AND r.rid=u.rid",
              cr->scid, snid) == 0)
        send_lang(u, s, NICK_X_HAS_NO_ROLE_ON_X, rnick, chname);
      else 
        {
          u_int32_t roleid = atoi(sql_field(0));
          if((source_snid != cr->founder) /* its not founder */
              && !is_master(source_snid, roleid)) /* its not master */
            send_lang(u,s, NOT_MASTER_OF_NICK_X_ON_X, rnick, cr->name);
          else if(sql_execute("UPDATE cs_role_users SET message=%s"
                  " WHERE snid=%d AND rid=%d", sql_str(rmsg), snid, roleid)>0)
            {
              if(rmsg)
              send_lang(u, s, MESSAGE_FOR_X_ON_X_CHANGED_TO_X, 
                rnick, chname, rmsg);
              else
                send_lang(u, s, MESSAGE_FOR_X_ON_X_REMOVED, 
                  rnick, chname);
            }
        }
    }
  else if(strcasecmp(cmd, "SET") == 0) /* SET role MASTER PROP VALUE */
    {
      char *rname;
      char *option;
      char *value;
      u_int32_t roleid;
      
      rname = strtok(NULL, " ");
      option = strtok(NULL, " ");
      value = strtok(NULL, " ");
          
      if(IsNull(rname) || IsNull(option) || IsNull(value)||
          (strcasecmp(option,"MASTER") &&
          strcasecmp(option,"PROP")))
        send_lang(u, s, CHAN_ROLE_SET_SYNTAX);
      else if((source_snid != cr->founder)) /* it's not founder */
        send_lang(u,s, ONLY_FOUNDER_X, chname);          
      else if((roleid = find_role(cr->scid, rname)) == 0 )              
        send_lang(u, s, CHAN_ROLE_X_X_NOT_FOUND, rname, chname);
      else
        { 
          if(strcasecmp(option, "MASTER") == 0) /* master option */
            {
              u_int32_t masterid;
              
              if(sql_field(1) == NULL) /* this depends on last find_role() call !!! */
                send_lang(u, s, CANT_SET_ADMIN_MASTER);
              else if((masterid = find_role(cr->scid, value)) == 0 )
                send_lang(u, s, CHAN_ROLE_X_X_NOT_FOUND, value, chname);
              else if(masterid == roleid)
                send_lang(u, s, CHAN_ROLE_NO_SELF_MASTER);
              else if(role_is_master(roleid, masterid) != 0) 
                send_lang(u, s, CHAN_ROLE_X_ON_X_IS_MASTER_OF_X, 
                  rname, chname, value);
              else
                {
                  set_role_master(roleid, masterid);
                  send_lang(u, s, CHAN_ROLE_MASTER_X_ON_X_TO_X,
                    rname, chname, value);
                }
            }
          else if(strcasecmp(option, "PROP") == 0) /* master option */
            {
              u_int32_t iactions = 0;
              u_int32_t iperms = 0;
              char *actions = 0;
              char *perms = 0;
              char *valuecp;

              valuecp = strdup(value);
              actions = strtok(valuecp,":");
              perms = strtok(NULL," ");
              if(actions && ((valres = validate_options(actions, actions_mask, &iactions)) != NULL))
                send_lang(u, s, INVALID_ACTION_X, valres);
              else if(perms && ((valres = validate_options(perms, permissions_mask, &iperms)) != NULL))
                send_lang(u, s, INVALID_PRIVILEGE_X, valres);
              else
                {
                  set_role_prop(roleid, iactions, iperms);
                  send_lang(u, s, ROLE_X_PROP_SET_X, rname, value);
                }
              free(valuecp);
            }
        }
    }
  else if(strcasecmp(cmd,"ACCEPT") == 0) /* List all roles */
    {
      int r;
      r = sql_execute("UPDATE cs_role_users SET flags=flags & ~%d"
        " WHERE scid=%d and snid=%d and (flags & %d)", 
        CRF_PENDING, cr->scid, source_snid, CRF_PENDING);
      if(r==1)
        send_lang(u, s, ROLE_ON_X_ACCEPTED, cr->name);
      else
        send_lang(u, s, NO_ROLE_PENDING_ON_X, cr->name);
    }
  else if(strcasecmp(cmd,"REJECT") == 0) /* List all roles */
    {
      int r;
      r = sql_execute("UPDATE cs_role_users SET flags=%d"
        " WHERE scid=%d and snid=%d and (flags & %d)", 
        CRF_REJECTED, cr->scid, source_snid, CRF_PENDING);
      if(r==1)
        send_lang(u, s, ROLE_ON_X_REJECTED, cr->name);
      else
        send_lang(u, s, NO_ROLE_PENDING_ON_X, cr->name);
    }    
  else
    send_lang(u, s, CHAN_ROLE_SYNTAX);
  
  CloseCR(cr);
}    

/* this is called on event ch_reg_chan */
int ev_cs_role_chan_join(ChanRecord* cr, IRC_ChanNode* cn)
{
  u_int32_t acmask = 0; /* actions mask */
  IRC_Chan* chan;
  int has_role = 0;
  
  if((chan = irc_FindChan(cr->name)) == NULL)
    abort(); /* this should never happen */  
    
  /* just don't do nothing for stealtch users */
  if(irc_IsUMode(cn->user, UMODE_STEALTH))
    return 0;
  
  if(cn->user && cn->user->snid) /* it's a registered nick */
  {
    MYSQL_RES* res;
    res = sql_query("SELECT r.rid, r.actions, u.message  FROM cs_role r, cs_role_users u"
      " WHERE u.scid=%d AND u.snid=%d AND r.rid=u.rid AND (u.flags & %d)=0", 
      cr->scid, cn->user->snid, CRF_PENDING|CRF_REJECTED);
        
    /* There is one role for this user, lets call the actions on it */
    if(sql_next_row(res))
    {
      int ret;
      char *msg;
      OptionMask* op = actions_mask;
          
      has_role = 1;
      SDUP(msg,sql_field(2));
           
      acmask = atoi(sql_field(1)); /* get the action mask */                    
      /* Let's call actions that match mask */
      while(op->name)
      {
        if(acmask & op->value)
        {
          ret = ((int (*)(IRC_Chan*, IRC_ChanNode*, char*)) op->func)(chan, cn, msg);
          if(ret == -1)
          {
            FREE(msg);
            sql_free(res);
            return -1;
          }
        }
        ++op;
      }
      FREE(msg);
      cr->t_last_use = irc_CurrentTime;
      UpdateCR(cr);
    }      
    sql_free(res);
  } 
      
  /* channel is for forbidden, or it is suspended, or it is
     restricted and user has no role, take action */
  if(!irc_IsUMode(cn->user, UMODE_OPER) &&
    (IsSuspendedChan(cr) || (IsRestrictedChan(cr) && (has_role == 0))))
  {
    char mask[HOSTLEN+10]; 
    char *reason = ""; 
    /* check if we need to join cs to keep the ban */ 
    if(chan->users_count == 1)
    {
      irc_ChanJoin(csu->u, cr->name, CU_MODE_ADMIN|CU_MODE_OP);
      irc_AddCTimerEvent(chan, 30 , ev_cs_role_timer_part, 0);
    }
    /* Suspended */
    if(IsSuspendedChan(cr))
    {
      strcpy(mask, "*!*@*");
      reason = "Suspended";
    }
    else 
    if(IsRestrictedChan(cr) && has_role == 0)
    { 
      snprintf(mask, sizeof(mask), "*!*@%s", cn->user->publichost);
      reason = "Restricted";
    }
    /* Apply ban */
    irc_ChanMode(chan->local_user ? chan->local_user : csu->u, chan,
      "+b %s", mask);
    /* And kick user */
    irc_Kick(chan->local_user ? chan->local_user : csu->u, chan, cn->user,
      reason);
    mod_abort_event();
    return 0;
  }

  /* roles were checked, now remove op if it was not aop */
  if(irc_IsCNOp(cn) && !(acmask & A_AOP))
    irc_ChanUMode(chan->local_user ? chan->local_user : csu->u, chan, "-o" , cn->user);
    
  /* set +h if op on HelpChan */
  if((acmask & A_AOP) && HelpChan && (irccmp(chan->name, HelpChan) == 0))
    irc_SvsMode(cn->user, csu->u, "+h");
  return 0;
}

/** actions code goes here
  if an action returns -1 then the other actions should noe be checked
*/
int action_aadm(IRC_Chan *chan, IRC_ChanNode* cn, char* msg)
{
  if(!irc_IsCNAdmin(cn)) 
    irc_ChanUMode(chan->local_user ? chan->local_user : csu->u, chan, "+a" , cn->user);
  return 0;
};

int action_aop(IRC_Chan *chan, IRC_ChanNode* cn, char* msg)
{
  if(!irc_IsCNOp(cn))
    irc_ChanUMode(chan->local_user ? chan->local_user : csu->u, chan, "+o" , cn->user);
  return 0;
}

int action_ahop(IRC_Chan *chan, IRC_ChanNode* cn, char* msg)
{
  if(!irc_IsCNHOp(cn))
    irc_ChanUMode(chan->local_user ? chan->local_user : csu->u, chan, "+h" , cn->user);
  return 0;
}

int action_avoice(IRC_Chan *chan, IRC_ChanNode* cn, char* msg)
{
  if(!irc_IsCNVoice(cn))
    irc_ChanUMode(chan->local_user ? chan->local_user : csu->u, chan, "+v" , cn->user);
  return 0;
}

int action_notice(IRC_Chan *chan, IRC_ChanNode* cn, char* msg)
{
  if(msg && (irc_ConnectionStatus() == 2))
    irc_SendCNotice(chan, chan->local_user ? chan->local_user : csu->u, "%s", msg);
  return 0;
}

int action_msg(IRC_Chan *chan, IRC_ChanNode* cn, char* msg)
{
  if(msg && (irc_ConnectionStatus() == 2 ))
    irc_SendCMsg(chan, chan->local_user ? chan->local_user : csu->u, "%s", msg);
  return 0;
}

u_int32_t find_role(u_int32_t scid, char *rname)
{

  if(sql_singlequery("SELECT rid, master_rid FROM cs_role WHERE scid=%d and name=%s",
    scid, sql_str(rname)) == 0)
      return 0;
  	
  return atoi(sql_field(0));
}

int add_to_role(u_int32_t roleid, u_int32_t scid, u_int32_t snid, u_int32_t who, char* msg, int flags)
{
  return sql_execute("INSERT INTO cs_role_users VALUES(%d, %d, %d, %d, %s, %d)",
    roleid, snid, who, scid, sql_str(msg), flags);
}

int del_from_role(u_int32_t scid, u_int32_t snid)
{
  return sql_execute("DELETE FROM cs_role_users WHERE scid=%d AND snid=%d",
    scid, snid);
}

int del_roles_from(u_int32_t scid)
{
  return sql_execute("DELETE FROM cs_role_users WHERE scid=%d",
    scid);
}

/**
  returns the number of roles that exist for a given channel
*/
int roles_count(u_int32_t scid)
{
  int count;
  count = sql_singlequery("SELECT count(*) FROM cs_role WHERE scid=%d", scid);
  if(count > 0)
    return atoi(sql_field(0));    
  return 0;
}

/**
  returns the number of users with roles assigned on a given channel
*/
int users_count(u_int32_t scid)
{
  int count;
  count = sql_singlequery("SELECT count(*) FROM cs_role_users WHERE scid=%d", scid);
  if(count > 0)
    return atoi(sql_field(0));
  return 0;
}

/** checks if a given snid is master of a given rid
  Returns: 0 if not, >0 if it is
 */
int is_master(u_int32_t snid, u_int32_t rid)
{
  int r;

  while(rid != 0)
    {
      r = sql_singlequery("SELECT master_rid FROM cs_role WHERE rid=%d", rid);

      if((r == 0) || (sql_field(0) == NULL)) /* reached a masterless role */
        return 0;
      else
        rid = atoi(sql_field(0));
        
      /* check if snid is member of master */
      r = sql_singlequery("SELECT snid FROM cs_role_users WHERE rid=%d AND snid=%d", 
        rid, snid);
      if( r > 0)
        return 1;
    }
  return 0;
}

/** checks if a given snid is member or master of a given rid
  Returns: 0 if not
           >0 if yes
 */
int is_member_or_master(u_int32_t snid, u_int32_t rid)
{
  if(sql_singlequery("SELECT snid FROM cs_role_users WHERE rid=%d AND snid=%d", 
    rid, snid) > 0)
    return 1;
  else    
    return is_master(snid, rid); /* now check if it is master */
    
}

/** checks if roleid1 is master or above of roleid2
 */
int role_is_master(u_int32_t roleid1, u_int32_t roleid2)
{
  int r;
  u_int32_t rid;
  
  rid = roleid2;  
  while(rid != 0)
    {
      r = sql_singlequery("SELECT master_rid FROM cs_role WHERE rid=%d", rid);
      if((r == 0) || !sql_field(0)) /* reached a masterless role */
        return 0;
      else
        rid = atoi(sql_field(0));
      
      if(roleid1 == rid)
        return 1;
    } 
  return 0;
}

/** Sets role master */
void set_role_master(u_int32_t roleid, u_int32_t masterid)
{
  sql_execute("UPDATE cs_role SET master_rid=%d WHERE rid=%d",
    masterid, roleid);
}

/** Sets role properties */
void set_role_prop(u_int32_t roleid, u_int32_t actions, u_int32_t perms)
{
  sql_execute("UPDATE cs_role SET actions=%d, perms=%d WHERE rid=%d",
    actions, perms, roleid);
}
                      
/** Check if a given snid has a given permission
  0 = no
  >0 = yes
  */
int role_with_permission(u_int32_t scid, u_int32_t snid, int permission)
{
  int r;
  r = sql_singlequery("SELECT r.rid, r.perms  FROM cs_role r, cs_role_users u "
    "WHERE u.scid=%d AND u.snid=%d AND r.rid=u.rid AND (u.flags & %d)=0", 
    scid, snid, CRF_PENDING|CRF_REJECTED);    
  if(r>0)
    {
      int i;
      i = atoi(sql_field(1));
      if(i & permission)
        return 1;
    }
  return 0;
}

/* will add the default roles for channel without roles (from services2) */
void fix_channels_roles(void)
{
  MYSQL_RES* res;
  MYSQL_ROW row;
  MYSQL_ROW row2;
  MYSQL_RES* res2;
  u_int32_t admin_rid;
  u_int32_t operator_rid;
#ifdef HALFOPS  
  u_int32_t halfoperator_rid;
#endif  
  u_int32_t  voice_rid;
  
  /* expire code goes here */
  res = sql_query("SELECT scid, founder, name FROM chanserv");

  while((row = sql_next_row(res)))
    {
      u_int32_t scid = atoi(row[0]);
      u_int32_t founder_snid = 0;
      if(row[1] == NULL)
      {
        log_log(cs_log, mod_info.name, "Skipping impot on forbidden channel %s",
          row[2]);
        continue;
      }
      founder_snid = atoi(row[1]);
      if(sql_singlequery("SELECT COUNT(*) FROM cs_role WHERE scid=%d", scid) == 0)
        return;
      if(atoi(sql_field(0)) == 0)
        {
          /* create default roles */
          admin_rid = create_role(scid, "admin", 0, DEF_ADMIN_ACTIONS, DEF_ADMIN_PERMS);
          if(admin_rid == 0)
            return;
          add_to_role(admin_rid, scid, founder_snid, founder_snid, NULL, 0);

          operator_rid = create_role(scid, "operator", admin_rid, DEF_OPERATOR_ACTIONS, DEF_OPERATOR_PERMS);
          if(operator_rid == 0)
            return;  
#ifdef HALFOPS        
          halfoperator_rid = create_role(scid, "halfoperator", operator_rid, DEF_HALFOPERATOR_ACTIONS, DEF_HALFOPERATOR_PERMS);
          if(halfoperator_rid == 0)
            return;  
          voice_rid = create_role(scid, "voice", halfoperator_rid, DEF_VOICE_ACTIONS, DEF_VOICE_PERMS);
#else
          voice_rid = create_role(scid, "voice", operator_rid, DEF_VOICE_ACTIONS, DEF_VOICE_PERMS);
#endif          
          if(voice_rid == 0)
            return;
          /* import role users from role temporary map */
          res2 = sql_query("SELECT snid, who, rtype FROM cs_role_temp" 
            " WHERE scid=%d", scid);
          while((row2 = sql_next_row(res2)))
            {   
              u_int32_t rid;          
              switch(atoi(row2[2]))
                {
                  case 1: rid = admin_rid; break;
                  case 2: rid = operator_rid; break;
#ifdef HALFOPS                  
                  case 4: rid = halfoperator_rid; break;
#endif                  
                  case 3:
                  default: rid = voice_rid; break;
                }
              add_to_role(rid, scid, atoi(row2[0]), atoi(row2[1]), NULL, 0);
            }
          sql_free(res2);
        }
    }
    
  sql_free(res);
  return;
}

/* notify users about pending roles */
void ev_cs_role_nick_identify(IRC_User* u, u_int32_t* snid)
{
  MYSQL_RES *res;
  MYSQL_ROW row;
  int rowc = 0;
  res = sql_query("SELECT cr.name, c.name FROM chanserv c, cs_role cr, cs_role_users cru"
          " WHERE cru.snid=%d and (cru.flags & %d)"
          " AND cr.rid=cru.rid AND c.scid=cr.scid",
          *snid, CRF_PENDING);
  while((row = sql_next_row(res)))
    {
      ++rowc;
      send_lang(u, csu->u, ROLE_X_X_X_X_PENDING, row[0], row[1], row[1], row[1]);
    }  
  sql_free(res);
}

void ev_cs_role_timer_part(IRC_Chan* chan, int tag)
{
  /* check if we are still on chan */
  if(chan->local_user == csu->u)
    irc_ChanPart(csu->u, chan);
}
/* Check users on +o for secureops */
void ev_cs_role_op(IRC_Chan *chan, IRC_User *user)
{
  int r = 0;
  ChanRecord *cr = chan->sdata;
  
  /* we dont deop local users */
  if(irc_IsLocalUser(user))
    return ;
  
  if(cr && user->snid)
    r = sql_singlequery("SELECT r.rid, r.perms  FROM cs_role r, cs_role_users u "
      "WHERE u.scid=%d AND u.snid=%d AND r.rid=u.rid AND (u.flags & %d)=0",
          cr->scid, user->snid, CRF_PENDING|CRF_REJECTED);
  if(cr && IsSecureOps(cr) && ((user->snid == 0) || (r == 0)))
    irc_ChanUMode(chan->local_user ? chan->local_user : csu->u, chan, "-o" , user);
}

int sql_upgrade(int version, int post)
{ 
  MYSQL_RES *res;
  MYSQL_ROW row;
  switch(version)
  {
    case 3:
    if(!post) /* we need to check for "lost" roles */
    {
      int rowc = 0;
      res = sql_query("SELECT"
        " cs_role.rid, cs_role.scid FROM cs_role"
        " LEFT JOIN chanserv ON (cs_role.scid = chanserv.scid)"
        " WHERE cs_role.scid IS NOT NULL AND chanserv.scid IS NULL");
      while((row = sql_next_row(res)))
      {
        log_log(cs_log, mod_info.name, "Removing lost role %s for scid %s",
          row[0], row[1]);
        sql_execute("DELETE FROM cs_role_users WHERE rid=%s",
          row[0]);          
        sql_execute("DELETE FROM cs_role WHERE rid=%s",
          row[0]);
        ++rowc;
      }
      if(rowc)
        log_log(cs_log, mod_info.name, "Removed %d lost roles(s)", rowc);
      sql_free(res);    
    }
    break;
  }
  return 1;
}

/* End of Module */


syntax highlighted by Code2HTML, v. 0.9.1