/**********************************************************************
 * 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: module os_uevent

 *  $Id: os_uevent.c,v 1.9 2005/12/11 16:51:17 jpinto Exp $
*/

#include "module.h"
#include "dbconf.h"
#include "my_sql.h"
#include "ns_group.h"
#include "nsmacros.h"
#include "lang/os_uevent.lh"
#include "lang/common.lh"


SVS_Module mod_info =
 /* module, version, description */
{"os_uevent", "1.1",  "session limit module" };

/* Change Log
  1.1 - #71, UEVENT GLINE using wrong gline author
        #70, UEVENT KILL action
  1.0 - Initial version
*/

#define DB_VERSION	1

/** functions/events we require **/
ServiceUser* (*operserv_suser)(void);
static int e_expire = -1;
static int e_nick_identify = -1;
static int e_nick_register = -1;

MOD_REQUIRES
  DBCONF_FUNCTIONS
  MOD_FUNC(operserv_suser)
  MOD_FUNC(is_sadmin)
  MOD_FUNC(e_nick_identify)
  MOD_FUNC(e_nick_register)
  MOD_FUNC(e_expire) /* we need this to run the expire routines */  
MOD_END

/* internal data */
static DBMem* dbm_uevents;

typedef struct EventType_s EventType;
struct EventType_s
{
  char *str;
  int e_type;
};

enum 
{
  ON_CONNECT = 0,
  ON_JOIN,
  ON_LOGIN,
  ON_REGISTER
};

#define EVENT_NUM 4

const static EventType event_types[] = 
{
  { "ON_CONNECT" , ON_CONNECT},
  { "ON_JOIN", ON_JOIN},
  { "ON_LOGIN", ON_LOGIN},
  { "ON_REGISTER", ON_REGISTER},
  { NULL }
};

/* 
 * We use dbmem to make it simple to keep the events on mem/db
 * The event index was added to avoid a full scan of the event table
 *  - Lamego
 */
static u_int32_t event_index[EVENT_NUM][1024];
static int event_index_size[EVENT_NUM];

typedef struct ActionType_s ActionType;
struct ActionType_s
{
  char *str;
  int a_type;
  ActionHandler action;
};


#define A_MESSAGE	0
#define A_NOTICE	1
#define A_GLINE		2
#define A_UMODE		3
#define A_JOIN		4
#define A_KILL		5


static int a_message(IRC_User *u, char *param);
static int a_notice(IRC_User *u, char *param);
static int a_gline(IRC_User *u, char *param);
static int a_umode(IRC_User *u, char *param);
static int a_join(IRC_User *u, char *param);
static int a_kill(IRC_User *u, char *param);

const static ActionType action_types[] =
{
  { "MESSAGE", A_MESSAGE , (ActionHandler) a_message},
  { "NOTICE", A_NOTICE, (ActionHandler) a_notice },
  { "GLINE", A_GLINE, (ActionHandler) a_gline },
  { "UMODE", A_UMODE, (ActionHandler) a_umode },
  { "JOIN", A_UMODE, (ActionHandler) a_join },
  { "KILL", A_UMODE, (ActionHandler) a_kill },
  { NULL },
};

/* internal functions */
static int ev_uevents_expire(void* dummy1, void* dummy2);
static int find_event_type(char* event);
static int find_action_type(char* action);
static void ev_uevents_new_user(IRC_User* u, void *s);
static void ev_uevents_chan_join(IRC_Chan *chan , IRC_ChanNode *cnode);
static void build_event_index();

/* Local variables */
static ServiceUser *osu;
static int os_log;

/* Local functions  - commands */
static void os_uevent(IRC_User *s, IRC_User *u);
static void os_uevent_add(IRC_User *s, IRC_User *u);
static void os_uevent_del(IRC_User *s, IRC_User *u);
static void os_uevent_list(IRC_User *s, IRC_User *u);

/* Local functions - utility */
static void ev_os_uevent_nick_identify(IRC_User* u, u_int32_t *snid);
static void ev_os_uevent_nick_register(IRC_User *u, u_int32_t *snid);

/** load code **/
int mod_load(void)
{
  int r;
  /* initialize dbmem for uevents */
  dbm_uevents = dbmem_init("user_events", 128);
  r = sql_check_inst_upgrade(mod_info.name, DB_VERSION, NULL);
  
  if(r < 0)
    return -1;

  /* try to load existing exceptions */
  if(dbmem_load(dbm_uevents) < 0 )
    return -3;
    
  build_event_index();
  
  os_log = log_handle("operserv");
  osu = operserv_suser();  
  
  suser_add_cmd(osu, "UEVENT", os_uevent, 
    OS_UEVENT_SUMMARY, OS_UEVENT_HELP);

  /* Add help */
  suser_add_help(osu, "UEVENT EVENTS", OS_UEVENT_EVENT_LIST);
  suser_add_help(osu, "UEVENT ACTIONS",OS_UEVENT_ACTION_LIST);
  
  /* Add user events */
  irc_AddEvent(ET_NEW_USER, ev_uevents_new_user); /* new user */
  irc_AddEvent(ET_CHAN_JOIN, ev_uevents_chan_join);

  /* Add event actions */ 
  mod_add_event_action(e_expire, 
    (ActionHandler) ev_uevents_expire);
  mod_add_event_action(e_nick_identify, 
    (ActionHandler) ev_os_uevent_nick_identify);
  mod_add_event_action(e_nick_register,
    (ActionHandler) ev_os_uevent_nick_register);  
  return 0;
}

void mod_unload(void)
{
  irc_DelEvent(ET_NEW_USER, ev_uevents_new_user);
  irc_DelEvent(ET_CHAN_JOIN, ev_uevents_chan_join);
  mod_del_event_action(e_expire, (ActionHandler) ev_uevents_expire);
  mod_del_event_action(e_nick_identify, (ActionHandler) ev_os_uevent_nick_identify);
  mod_del_event_action(e_nick_register, (ActionHandler) ev_os_uevent_nick_register);  
  dbmem_free(dbm_uevents);
  suser_del_mod_cmds(osu, &mod_info);
}

void os_uevent(IRC_User *s, IRC_User *u)
{
  char* cmd;
  u_int32_t source_snid;

  CHECK_IF_IDENTIFIED_NICK

  if (!is_sadmin(u->snid))
  {
    send_lang(u, s, PERMISSION_DENIED);
    return;
  }

  cmd = strtok(NULL, " ");
  /* check syntax */
  if(!cmd)
    send_lang(u, s, OS_UEVENT_SYNTAX);
  else
  if(!strcasecmp(cmd, "ADD"))
    os_uevent_add(s, u);    
  else
  if(!strcasecmp(cmd, "DEL"))
    os_uevent_del(s, u);
  else  
  if(!strcasecmp(cmd, "LIST"))
    os_uevent_list(s, u);
  else
    send_lang(u, s, OS_UEVENT_SYNTAX);    
}

void os_uevent_add(IRC_User *s, IRC_User *u)
{
  char *event, *event_parm = NULL;
  char* time_string = "";  
  int duration = 0;  
  char *action, *action_parm = NULL;
  int event_i, action_i;
  
  event  = strtok(NULL, " ");
  if(event && event[0]=='+')
  {
    time_string = event;
    duration = ftime_str(event);
    event  = strtok(NULL, " ");
  }
  if(event)
  {
    char* e = strchr(event, '(');
    if(e)
    {
      *e = '\0';
      event_parm = e+1;
      e = strchr(event_parm, ')');
      if(e)
       *e = '\0';
    }
  }
  
  action = strtok(NULL, " ");
  action_parm = strtok(NULL, "");
  if(!event || ! action || !action_parm)
    send_lang(u, s, OS_UEVENT_ADD_INVALID_SYNTAX);
  else
  /* check if time is valid */
  if(duration == -1)
    send_lang(u, s, INVALID_TIME_X, time_string);
  else
  if((event_i = find_event_type(event)) == -1)
    send_lang(u, s, OS_UEVENT_INVALID_EVENT_X, event);
  else
  if((action_i = find_action_type(action)) == -1)
    send_lang(u, s, OS_UEVENT_INVALID_ACTION_X, action);
  else
  {
    int i = 0;
    char* d_row[7];
    d_row[i++] = itoa(0); /* id */
    d_row[i++] = itoa(irc_CurrentTime); /* t_when */
    d_row[i++] = itoa(duration);       /* duration */
    d_row[i++] = itoa(event_i);
    d_row[i++] = event_parm;
    d_row[i++] = itoa(action_i);
    d_row[i++] = action_parm;
    if(dbmem_insert(dbm_uevents, d_row) < 0)
      send_lang(u, s, UPDATE_FAIL);
    else
    {
      send_lang(u, s, OS_UEVENT_ADD_OK);    
      build_event_index();
    }
  }
}

/* OS UEVENT DEL command */
void os_uevent_del(IRC_User *s, IRC_User *u)
{
  char* str;
  u_int32_t id = 0;
  str = strtok(NULL, " ");
  if(str)
    id = atoi(str);
  else
  {
    send_lang(u, s, OS_UEVENT_DEL_SYNTAX);
    return;
  }
  if(dbmem_find_exact(dbm_uevents, itoa(id), 0) == NULL)
    send_lang(u, s, OS_UEVENT_DEL_NOT_FOUND_X, id);
  else
  if(dbmem_delete_current(dbm_uevents) < 0)
    send_lang(u, s, UPDATE_FAIL);
  else
  {
    send_lang(u, s, OS_UEVENT_DEL_DELETED_X, id);
    build_event_index();
  }
}

void os_uevent_list(IRC_User *s, IRC_User *u)
{
  char **row;
  ev_uevents_expire(NULL, NULL);
  send_lang(u, s, OS_UEVENT_LIST_HEADER);
  row = dbmem_first_row(dbm_uevents);
  while(row)
  {
    char* event = event_types[atoi(row[3])].str;
    char* action = action_types[atoi(row[5])].str;
    send_lang(u, s, OS_UEVENT_LIST_X_X_X_X,
      atoi(row[0]), event, row[4] ? row[4] : "", action, row[6]);
    row = dbmem_next_row(dbm_uevents);
  };
  send_lang(u, s, OS_UEVENT_LIST_TAIL);  
}

static void build_event_index()
{
  char **row;
  int e_type;
  int i;
  row = dbmem_first_row(dbm_uevents);
  for(i = 0; i < EVENT_NUM; ++i)
    event_index_size[i] = 0;
    
  i = 0;  
  while(row)
  {
    e_type = atoi(row[3]);
    if(event_index_size[e_type] == 1023)
    {
      errlog("Exceeded hash capacity on build_event_index() !");
      return;
    }
    event_index[e_type][event_index_size[e_type]++] = i++;
    row = dbmem_next_row(dbm_uevents);
  }
}
/* ev_exceptions_expire - deleted expired exceptions
 *  returns:
 *	Number of imported rows, <0 for error
 */
int ev_uevents_expire(void* dummy1, void* dummy2)
{
  dbmem_expire(dbm_uevents, 1, 2);
  build_event_index();
  return 0;
}

static int a_message(IRC_User *u, char *param)
{
  irc_SendMsg(u, osu->u, "%s", param);
  return 0;
}

static int a_notice(IRC_User *u, char *param)
{
  irc_SendNotice(u, osu->u, "%s", param);
  return 0;
}

static int a_gline(IRC_User *u, char *param)
{
  irc_Gline(osu->u, osu->u->nick, u->realhost, 24*3600,
          param ? param : "");
  return 0;
}

static int a_umode(IRC_User *u, char *param)
{
  irc_SvsMode(u, osu->u, param);
  return 0;
}

static int a_join(IRC_User *u, char *param)
{
  irc_SvsJoin(u, osu->u, param);
  return 0;
}

static int a_kill(IRC_User *u, char *param)
{
  irc_Kill(u, osu->u, param ? param : "");
  irc_AbortThisEvent();
  return 0;
}

static int find_event_type(char* event)
{
 int i = 0;
 while(event_types[i].str)
 {
   if(strcasecmp(event_types[i].str, event) == 0)
     return i;
   i++;
 }
 return -1;
}

static int find_action_type(char* action)
{
 int i = 0;
 while(action_types[i].str)
 {
   if(strcasecmp(action_types[i].str, action) == 0)
     return i;
   i++;
 }
 return -1;
}

/* a new user is connecting, we need to trigger the ON_CONNECT events  */
static void ev_uevents_new_user(IRC_User* u, void *s)
{
  char **row;
  char* mask = irc_UserMask(u);
  char* maskp = irc_UserMaskP(u);
  int i;
  for(i = 0; i < event_index_size[ON_CONNECT]; ++i)
  {
    row = dbmem_row_at(dbm_uevents, event_index[ON_CONNECT][i]);
    if((row[4] == NULL) || (row[4] && 
      (match(row[4], mask) || match(row[4], maskp))))
      {
        /* do the action here */
        action_types[atoi(row[5])].action(u, row[6]);
      }
  }
}

static void ev_uevents_chan_join(IRC_Chan *chan , IRC_ChanNode *cnode)
{
  char **row;
  int i;
  for(i = 0; i < event_index_size[ON_JOIN]; ++i)
  {
    row = dbmem_row_at(dbm_uevents, event_index[ON_JOIN][i]);
    if((row[4] == NULL) || (row[4] && match(row[4], chan->name)))
      {
        /* do the action here */
        action_types[atoi(row[5])].action(cnode->user, row[6]);
      }
  }
}

static void ev_os_uevent_nick_identify(IRC_User* u, u_int32_t *snid)
{
  char **row;
  char* mask = irc_UserMask(u);
  char* maskp = irc_UserMaskP(u);
  int i;
  for(i = 0; i < event_index_size[ON_LOGIN]; ++i)
  {
    row = dbmem_row_at(dbm_uevents, event_index[ON_LOGIN][i]);
    if((row[4] == NULL) || (row[4] && 
      (match(row[4], mask) || match(row[4], maskp))))
      {
        /* do the action here */
        action_types[atoi(row[5])].action(u, row[6]);
      }
  }
}

static void ev_os_uevent_nick_register(IRC_User *u, u_int32_t *snid)
{
  char **row;
  char* mask = irc_UserMask(u);
  char* maskp = irc_UserMaskP(u);
  int i;
  for(i = 0; i < event_index_size[ON_REGISTER]; ++i)
  {
    row = dbmem_row_at(dbm_uevents, event_index[ON_REGISTER][i]);
    if((row[4] == NULL) || (row[4] && 
      (match(row[4], mask) || match(row[4], maskp))))
      {
        /* do the action here */
        action_types[atoi(row[5])].action(u, row[6]);
      }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1