/*
 * tclmisc.c -- handles:
 *   Tcl stubs for everything else
 */
/*
 * Copyright (C) 1997 Robey Pointer
 * Copyright (C) 1999 - 2025 Eggheads Development Team
 *
 * 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 2
 * of the License, 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include <errno.h>
#include "main.h"
#include "modules.h"
#include "md5/md5.h"
#include <sys/stat.h>
#include <sys/utsname.h>

extern p_tcl_bind_list bind_table_list;
extern tcl_timer_t *timer, *utimer;
extern struct dcc_t *dcc;
extern char botnetnick[], quit_msg[];
extern struct userrec *userlist;
extern time_t now;
extern module_entry *module_list;
extern int max_logs, cache_hit, cache_miss;
extern log_t *logs;
extern Tcl_Interp *interp;

int expmem_tclmisc()
{
  int i, tot = 0;

  for (i = 0; i < max_logs; i++) {
    if (logs[i].filename != NULL) {
      tot += strlen(logs[i].filename) + 1;
      tot += strlen(logs[i].chname) + 1;
    }
  }

  return tot;
}

/*
 *      Logging
 */

/* logfile [<modes> <channel> <filename>] */
static int tcl_logfile STDVAR
{
  int i;
  char s[256];

  BADARGS(1, 4, " ?logModes channel logFile?");

  if (argc == 1) {
    /* They just want a list of the logfiles and modes */
    for (i = 0; i < max_logs; i++)
      if (logs[i].filename != NULL) {
        snprintf(s, sizeof s, "%s %s %s", masktype(logs[i].mask),
                 logs[i].chname, logs[i].filename);
        Tcl_AppendElement(interp, s);
      }
    return TCL_OK;
  }

  BADARGS(4, 4, " ?logModes channel logFile?");

  if (*argv[1] && !*argv[2]) {
    Tcl_AppendResult(interp,
                     "log modes set, but no channel specified", NULL);
    return TCL_ERROR;
  }
  if (*argv[2] && !strchr(CHANMETA, *argv[2]) && strcmp(argv[2], "*")) {
    Tcl_AppendResult(interp, "invalid channel prefix", NULL);
    return TCL_ERROR;
  }
  if (*argv[2] && strchr(argv[2], ' ')) {
    Tcl_AppendResult(interp, "channel names cannot contain spaces", NULL);
    return TCL_ERROR;
  }

  for (i = 0; i < max_logs; i++)
    if ((logs[i].filename != NULL) && (!strcmp(logs[i].filename, argv[3]))) {
      logs[i].flags &= ~LF_EXPIRING;
      logs[i].mask = logmodes(argv[1]);
      nfree(logs[i].chname);
      logs[i].chname = NULL;
      if (!logs[i].mask) {
        /* ending logfile */
        nfree(logs[i].filename);
        logs[i].filename = NULL;
        if (logs[i].f != NULL) {
          fclose(logs[i].f);
          logs[i].f = NULL;
        }
        logs[i].flags = 0;
      } else {
        logs[i].chname = nmalloc(strlen(argv[2]) + 1);
        strcpy(logs[i].chname, argv[2]);
      }
      Tcl_AppendResult(interp, argv[3], NULL);
      return TCL_OK;
    }
  /* Do not add logfiles without any flags to log ++rtc */
  if (!logmodes(argv[1])) {
    Tcl_AppendResult(interp, "can't remove \"", argv[3],
                     "\" from list: no such logfile", NULL);
    return TCL_ERROR;
  }
  for (i = 0; i < max_logs; i++)
    if (logs[i].filename == NULL) {
      logs[i].flags = 0;
      logs[i].mask = logmodes(argv[1]);
      logs[i].filename = nmalloc(strlen(argv[3]) + 1);
      strcpy(logs[i].filename, argv[3]);
      logs[i].chname = nmalloc(strlen(argv[2]) + 1);
      strcpy(logs[i].chname, argv[2]);
      Tcl_AppendResult(interp, argv[3], NULL);
      return TCL_OK;
    }
  Tcl_AppendResult(interp, "reached max # of logfiles", NULL);
  return TCL_ERROR;
}

static int tcl_putlog STDVAR
{
  char logtext[501];

  BADARGS(2, 2, " text");

  strlcpy(logtext, argv[1], sizeof logtext);
  putlog(LOG_MISC, "*", "%s", logtext);
  return TCL_OK;
}

static int tcl_putcmdlog STDVAR
{
  char logtext[501];

  BADARGS(2, 2, " text");

  strlcpy(logtext, argv[1], sizeof logtext);
  putlog(LOG_CMDS, "*", "%s", logtext);
  return TCL_OK;
}

static int tcl_putxferlog STDVAR
{
  char logtext[501];

  BADARGS(2, 2, " text");

  strlcpy(logtext, argv[1], sizeof logtext);
  putlog(LOG_FILES, "*", "%s", logtext);
  return TCL_OK;
}

static int tcl_putloglev STDVAR
{
  int lev = 0;
  char logtext[501];

  BADARGS(4, 4, " flag(s) channel text");

  lev = logmodes(argv[1]);
  if (!lev) {
    Tcl_AppendResult(irp, "No valid log flag given", NULL);
    return TCL_ERROR;
  }
  strlcpy(logtext, argv[3], sizeof logtext);

  putlog(lev, argv[2], "%s", logtext);
  return TCL_OK;
}

static int tcl_binds STDVAR
{
  int matching = 0;
  char *g, flg[100], hits[11];
  EGG_CONST char *list[5];
  tcl_bind_list_t *tl, *tl_kind;
  tcl_bind_mask_t *tm;
  tcl_cmd_t *tc;

  BADARGS(1, 2, " ?type/mask?");

  if (argv[1])
    tl_kind = find_bind_table(argv[1]);
  else
    tl_kind = NULL;
  if (!tl_kind && argv[1])
    matching = 1;
  for (tl = tl_kind ? tl_kind : bind_table_list; tl;
       tl = tl_kind ? 0 : tl->next) {
    if (tl->flags & HT_DELETED)
      continue;
    for (tm = tl->first; tm; tm = tm->next) {
      if (tm->flags & TBM_DELETED)
        continue;
      for (tc = tm->first; tc; tc = tc->next) {
        if (tc->attributes & TC_DELETED)
          continue;
        if (matching &&
            !wild_match_per(argv[1], tl->name) &&
            !wild_match_per(argv[1], tm->mask) &&
            !wild_match_per(argv[1], tc->func_name))
          continue;
        build_flags(flg, &(tc->flags), NULL);
        egg_snprintf(hits, sizeof hits, "%i", (int) tc->hits);
        list[0] = tl->name;
        list[1] = flg;
        list[2] = tm->mask;
        list[3] = hits;
        list[4] = tc->func_name;
        g = Tcl_Merge(5, list);
        Tcl_AppendElement(irp, g);
        Tcl_Free((char *) g);
      }
    }
  }
  return TCL_OK;
}

int check_timer_syntax(Tcl_Interp *irp, int argc, char *argv[], tcl_timer_t *stack) {
  char *endptr;
  long val;

  if (atoi(argv[1]) < 0) {
    Tcl_AppendResult(irp, "time value must be positive", NULL);
    return 1;
  }
  if (argc >=4) {
    val = strtol(argv[3], &endptr, 0);
    if ((*endptr != '\0') || (val < 0)) {
      Tcl_AppendResult(irp, "count value must be >=0", NULL);
      return 1;
    }
  }
  if (argv[2][0] != '#') {
    if (argc == 5) {
      /* Prevent collisions with unnamed timers */
      if (strncmp(argv[4], "timer", 5) == 0) {
        Tcl_AppendResult(irp, "timer name may not begin with \"timer\"", NULL);
        return 1;
      }
      /* Check for existing timers by same name */
      if (find_timer(stack, argv[4])) {
        Tcl_AppendResult(irp, "timer already exists by that name", NULL);
        return 1;
      }
    }
  }
  return 0;
}

static int tcl_timer STDVAR
{
  char *x;

  BADARGS(3, 5, " minutes command ?count ?name??");

  if (check_timer_syntax(irp, argc, argv, timer)) {
    return TCL_ERROR;
  }
  x = add_timer(&timer, atoi(argv[1]), (argc >= 4 ? atoi(argv[3]) : 1),
                  argv[2], (argc == 5 ? argv[4] : NULL), 0L);
  if (!x) {
    Tcl_AppendResult(irp, "Too many timers (wow, impressive). Timer not added", NULL);
    return TCL_ERROR;
  }
  Tcl_AppendResult(irp, x, NULL);
  return TCL_OK;
}

static int tcl_utimer STDVAR
{
  char *x;

  BADARGS(3, 5, " seconds command ?count ?name??");

  if (check_timer_syntax(irp, argc, argv, utimer)) {
    return TCL_ERROR;
  }
  x = add_timer(&utimer, atoi(argv[1]), (argc >= 4 ? atoi(argv[3]) : 1),
                  argv[2], (argc == 5 ? argv[4] : '\0'), 0L);
  if (!x) {
    Tcl_AppendResult(irp, "Too many timers (wow, impressive). Timer not added", NULL);
    return TCL_ERROR;
  }
  Tcl_AppendResult(irp, x, NULL);
  return TCL_OK;
}

static int tcl_killtimer STDVAR
{
  BADARGS(2, 2, " timerID");

  if (remove_timer(&timer, argv[1]))
    return TCL_OK;
  Tcl_AppendResult(irp, "invalid timer name", NULL);
  return TCL_ERROR;
}

static int tcl_killutimer STDVAR
{
  BADARGS(2, 2, " timerName");

  if (remove_timer(&utimer, argv[1])) {
    return TCL_OK;
  }
  Tcl_AppendResult(irp, "invalid utimer name", NULL);
  return TCL_ERROR;
}

static int tcl_timers STDVAR
{
  BADARGS(1, 1, "");

  list_timers(irp, timer);
  return TCL_OK;
}

static int tcl_utimers STDVAR
{
  BADARGS(1, 1, "");

  list_timers(irp, utimer);
  return TCL_OK;
}

static int tcl_duration STDVAR
{
  char s[70];
  unsigned long sec, tmp;

  BADARGS(2, 2, " seconds");

  if (atol(argv[1]) <= 0) {
    Tcl_AppendResult(irp, "0 seconds", NULL);
    return TCL_OK;
  }
  sec = atol(argv[1]);

  s[0] = 0;
  if (sec >= 31536000) {
    tmp = (sec / 31536000);
    sprintf(s, "%lu year%s ", tmp, (tmp == 1) ? "" : "s");
    sec -= (tmp * 31536000);
  }
  if (sec >= 604800) {
    tmp = (sec / 604800);
    sprintf(&s[strlen(s)], "%lu week%s ", tmp, (tmp == 1) ? "" : "s");
    sec -= (tmp * 604800);
  }
  if (sec >= 86400) {
    tmp = (sec / 86400);
    sprintf(&s[strlen(s)], "%lu day%s ", tmp, (tmp == 1) ? "" : "s");
    sec -= (tmp * 86400);
  }
  if (sec >= 3600) {
    tmp = (sec / 3600);
    sprintf(&s[strlen(s)], "%lu hour%s ", tmp, (tmp == 1) ? "" : "s");
    sec -= (tmp * 3600);
  }
  if (sec >= 60) {
    tmp = (sec / 60);
    sprintf(&s[strlen(s)], "%lu minute%s ", tmp, (tmp == 1) ? "" : "s");
    sec -= (tmp * 60);
  }
  if (sec > 0) {
    tmp = (sec);
    sprintf(&s[strlen(s)], "%lu second%s", tmp, (tmp == 1) ? "" : "s");
  }
  if (strlen(s) > 0 && s[strlen(s) - 1] == ' ')
    s[strlen(s) - 1] = 0;
  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_unixtime STDVAR
{
  char s[21];
  time_t now2 = time(NULL);

  BADARGS(1, 1, "");

  snprintf(s, sizeof s, "%" PRId64, (int64_t) now2);
  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_ctime STDVAR
{
  time_t tt;
  char s[26];

  BADARGS(2, 2, " unixtime");

  tt = (time_t) atol(argv[1]);
  ctime_r(&tt, s);
  s[24] = 0;
  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_strftime STDVAR
{
  char buf[512];
  struct tm *tm1;
  time_t t;

  BADARGS(2, 3, " format ?time?");

  if (argc == 3)
    t = atol(argv[2]);
  else
    t = now;
  tm1 = localtime(&t);
  if (!tm1) {
    Tcl_AppendResult(irp, "tcl_strftime(): localtime(): error = ",
                     strerror(errno), NULL);
    return TCL_ERROR;
  }
  if (strftime(buf, sizeof(buf) - 1, argv[1], tm1)) {
    Tcl_AppendResult(irp, buf, NULL);
    return TCL_OK;
  }
  Tcl_AppendResult(irp, "tcl_strftime(): strftime(): error", NULL);
  return TCL_ERROR;
}

static int tcl_myip STDVAR
{
  char s[EGG_INET_ADDRSTRLEN];

  BADARGS(1, 1, "");

  getdccaddr(NULL, s, sizeof s);
  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_rand STDVAR
{
  long i;
  unsigned long x;
  char s[11];

  BADARGS(2, 2, " limit");

  i = atol(argv[1]);

  if (i <= 0) {
    Tcl_AppendResult(irp, "random limit must be greater than 0", NULL);
    return TCL_ERROR;
  } else if (i > RANDOM_MAX) {
    Tcl_AppendResult(irp, "random limit must be equal to or less than ",
                     int_to_base10(RANDOM_MAX), NULL);
    return TCL_ERROR;
  }

  x = randint(i);

  egg_snprintf(s, sizeof s, "%lu", x);

  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_sendnote STDVAR
{
  char s[5], from[NOTENAMELEN + 1], to[NOTENAMELEN + 1], msg[451];

  BADARGS(4, 4, " from to message");

  strlcpy(from, argv[1], sizeof from);
  strlcpy(to, argv[2], sizeof to);
  strlcpy(msg, argv[3], sizeof msg);
  egg_snprintf(s, sizeof s, "%d", add_note(to, from, msg, -1, 0));
  Tcl_AppendResult(irp, s, NULL);
  return TCL_OK;
}

static int tcl_dumpfile STDVAR
{
  char nick[NICKLEN];
  struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };

  BADARGS(3, 3, " nickname filename");

  strlcpy(nick, argv[1], sizeof nick);
  get_user_flagrec(get_user_by_nick(nick), &fr, NULL);
  showhelp(argv[1], argv[2], &fr, HELP_TEXT);
  return TCL_OK;
}

static int tcl_dccdumpfile STDVAR
{
  int idx, i;
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };

  BADARGS(3, 3, " idx filename");

  i = atoi(argv[1]);
  idx = findidx(i);
  if (idx < 0) {
    Tcl_AppendResult(irp, "illegal idx", NULL);
    return TCL_ERROR;
  }
  get_user_flagrec(get_user_by_handle(userlist, dcc[idx].nick), &fr, NULL);

  tellhelp(idx, argv[2], &fr, HELP_TEXT);
  return TCL_OK;
}

static int tcl_backup STDVAR
{
  BADARGS(1, 1, "");

  call_hook(HOOK_BACKUP);
  return TCL_OK;
}

static int tcl_die STDVAR
{
  char s[1024];

  BADARGS(1, 2, " ?reason?");

  if (argc == 2) {
    egg_snprintf(s, sizeof s, "BOT SHUTDOWN (%s)", argv[1]);
    strlcpy(quit_msg, argv[1], 1024);
  } else {
    strlcpy(s, "BOT SHUTDOWN (No reason)", sizeof s);
    quit_msg[0] = 0;
  }
  kill_bot(s, quit_msg[0] ? quit_msg : "EXIT");
  return TCL_OK;
}

static int tcl_loadmodule STDVAR
{
  const char *p;

  BADARGS(2, 2, " module-name");

  p = module_load(argv[1]);
  if (p && strcmp(p, MOD_ALREADYLOAD) && !strcmp(argv[0], "loadmodule"))
    putlog(LOG_MISC, "*", "%s %s: %s", MOD_CANTLOADMOD, argv[1], p);
  Tcl_AppendResult(irp, p, NULL);
  return TCL_OK;
}

static int tcl_unloadmodule STDVAR
{
  BADARGS(2, 2, " module-name");

  Tcl_AppendResult(irp, module_unload(argv[1], botnetnick), NULL);
  return TCL_OK;
}

static int tcl_unames STDVAR
{
  Tcl_AppendResult(irp, egg_uname(), NULL);
  return TCL_OK;
}

static int tcl_modules STDVAR
{
  int i;
  char *p, s[24], s2[24];
  EGG_CONST char *list[100], *list2[2];
  dependancy *dep;
  module_entry *current;

  BADARGS(1, 1, "");

  for (current = module_list; current; current = current->next) {
    list[0] = current->name;
    egg_snprintf(s, sizeof s, "%d.%d", current->major, current->minor);
    list[1] = s;
    i = 2;
    for (dep = dependancy_list; dep && (i < 100); dep = dep->next) {
      if (dep->needing == current) {
        list2[0] = dep->needed->name;
        egg_snprintf(s2, sizeof s2, "%d.%d", dep->major, dep->minor);
        list2[1] = s2;
        list[i] = Tcl_Merge(2, list2);
        i++;
      }
    }
    p = Tcl_Merge(i, list);
    Tcl_AppendElement(irp, p);
    Tcl_Free((char *) p);
    while (i > 2) {
      i--;
      Tcl_Free((char *) list[i]);
    }
  }
  return TCL_OK;
}

static int tcl_loadhelp STDVAR
{
  BADARGS(2, 2, " helpfile-name");

  add_help_reference(argv[1]);
  return TCL_OK;
}

static int tcl_unloadhelp STDVAR
{
  BADARGS(2, 2, " helpfile-name");

  rem_help_reference(argv[1]);
  return TCL_OK;
}

static int tcl_reloadhelp STDVAR
{
  BADARGS(1, 1, "");

  reload_help_data();
  return TCL_OK;
}

static int tcl_callevent STDVAR
{
  BADARGS(2, 3, " event ?arg?");

  if (argc == 2) {
    check_tcl_event(argv[1]);
  } else {
    check_tcl_event_arg(argv[1], argv[2]);
  }
  return TCL_OK;
}

static int tcl_stripcodes STDVAR
{
  int flags = 0;
  char *p;

  BADARGS(3, 3, " strip-flags string");

  for (p = argv[1]; *p; p++)
    switch (*p) {
    case 'c':
      flags |= STRIP_COLOR;
      break;
    case 'b':
      flags |= STRIP_BOLD;
      break;
    case 'r':
      flags |= STRIP_REVERSE;
      break;
    case 'u':
      flags |= STRIP_UNDERLINE;
      break;
    case 'a':
      flags |= STRIP_ANSI;
      break;
    case 'g':
      flags |= STRIP_BELLS;
      break;
    case 'o':
      flags |= STRIP_ORDINARY;
      break;
    case 'i':
      flags |= STRIP_ITALICS;
      break;
    case '*':
      flags |= STRIP_ALL;
      break;
    default:
      Tcl_AppendResult(irp, "Invalid strip-flags: ", argv[1], NULL);
      return TCL_ERROR;
    }

  p = Tcl_Alloc(strlen(argv[2]) + 1);
  strcpy(p, argv[2]);
  strip_mirc_codes(flags, p);
  Tcl_SetResult(irp, p, TCL_DYNAMIC);
  return TCL_OK;
}

static int tcl_md5 STDVAR
{
  char digest_string[33];
  unsigned char digest[16];
  int i;

  BADARGS(2, 2, " string");

#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && defined(HAVE_EVP_MD5)
  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
  const EVP_MD *md = EVP_md5();
  unsigned int md_len;
  EVP_DigestInit_ex(mdctx, md, NULL);
  EVP_DigestUpdate(mdctx, argv[1], strlen(argv[1]));
  EVP_DigestFinal_ex(mdctx, digest, &md_len);
  EVP_MD_CTX_free(mdctx);
#else
  MD5_CTX md5context;
  MD5_Init(&md5context);
  MD5_Update(&md5context, (unsigned char *) argv[1], strlen(argv[1]));
  MD5_Final(digest, &md5context);
#endif

  for (i = 0; i < 16; i++)
    sprintf(digest_string + (i * 2), "%.2x", digest[i]);
  Tcl_AppendResult(irp, digest_string, NULL);
  return TCL_OK;
}

static int tcl_matchaddr STDVAR
{
  BADARGS(3, 3, " mask address");

  if (match_addr(argv[1], argv[2]))
    Tcl_AppendResult(irp, "1", NULL);
  else
    Tcl_AppendResult(irp, "0", NULL);
  return TCL_OK;
}

static int tcl_matchcidr STDVAR
{
  BADARGS(4, 4, " block address prefix");

  if (cidr_match(argv[1], argv[2], atoi(argv[3])))
    Tcl_AppendResult(irp, "1", NULL);
  else
    Tcl_AppendResult(irp, "0", NULL);
  return TCL_OK;
}

static int tcl_matchstr STDVAR
{
  BADARGS(3, 3, " pattern string");

  if (wild_match(argv[1], argv[2]))
    Tcl_AppendResult(irp, "1", NULL);
  else
    Tcl_AppendResult(irp, "0", NULL);
  return TCL_OK;
}

static int tcl_status STDVAR
{
  char s[15];

  BADARGS(1, 2, " ?type?");

  if ((argc < 2) || !strcmp(argv[1], "cpu")) {
    Tcl_AppendElement(irp, "cputime");
    egg_snprintf(s, sizeof s, "%f", getcputime());
    Tcl_AppendElement(irp, s);
  }
  if ((argc < 2) || !strcmp(argv[1], "mem")) {
    Tcl_AppendElement(irp, "expmem");
    egg_snprintf(s, sizeof s, "%d", expected_memory());
    Tcl_AppendElement(irp, s);
  }
  if ((argc < 2) || !strcmp(argv[1], "ipv6")) {
    Tcl_AppendElement(irp, "ipv6");
#ifdef IPV6
    Tcl_AppendElement(irp, "enabled");
#else
    Tcl_AppendElement(irp, "disabled");
#endif
  }
  if ((argc < 2) || !strcmp(argv[1], "tls")) {
    Tcl_AppendElement(irp, "tls");
#ifdef TLS
  #if OPENSSL_VERSION_NUMBER >= 0x10100000L /* 1.1.0 */
    Tcl_AppendElement(irp, OpenSSL_version(OPENSSL_VERSION));
  #else
    Tcl_AppendElement(irp, SSLeay_version(SSLEAY_VERSION));
  #endif
#else
    Tcl_AppendElement(irp, "disabled");
#endif
  }
  if ((argc < 2) || !strcmp(argv[1], "cache")) {
    Tcl_AppendElement(irp, "usercache");
    egg_snprintf(s, sizeof s, "%4.1f", 100.0 *
             ((float) cache_hit) / ((float) (cache_hit + cache_miss)));
    Tcl_AppendElement(irp, s);
  }

  return TCL_OK;
}

static int tcl_rfcequal STDVAR
{
  BADARGS(3, 3, " string1 string2");

  Tcl_AppendResult(irp, !rfc_casecmp(argv[1], argv[2]) ? "1" : "0", NULL);

  return TCL_OK;
}

tcl_cmds tclmisc_cmds[] = {
  {"logfile",           tcl_logfile},
  {"putlog",             tcl_putlog},
  {"putcmdlog",       tcl_putcmdlog},
  {"putxferlog",     tcl_putxferlog},
  {"putloglev",       tcl_putloglev},
  {"timer",               tcl_timer},
  {"utimer",             tcl_utimer},
  {"killtimer",       tcl_killtimer},
  {"killutimer",     tcl_killutimer},
  {"timers",             tcl_timers},
  {"utimers",           tcl_utimers},
  {"unixtime",         tcl_unixtime},
  {"strftime",         tcl_strftime},
  {"ctime",               tcl_ctime},
  {"myip",                 tcl_myip},
  {"rand",                 tcl_rand},
  {"sendnote",         tcl_sendnote},
  {"dumpfile",         tcl_dumpfile},
  {"dccdumpfile",   tcl_dccdumpfile},
  {"backup",             tcl_backup},
  {"exit",                  tcl_die},
  {"die",                   tcl_die},
  {"unames",             tcl_unames},
  {"unloadmodule", tcl_unloadmodule},
  {"loadmodule",     tcl_loadmodule},
  {"checkmodule",    tcl_loadmodule},
  {"modules",           tcl_modules},
  {"loadhelp",         tcl_loadhelp},
  {"unloadhelp",     tcl_unloadhelp},
  {"reloadhelp",     tcl_reloadhelp},
  {"duration",         tcl_duration},
  {"binds",               tcl_binds},
  {"callevent",       tcl_callevent},
  {"stripcodes",     tcl_stripcodes},
  {"matchaddr",       tcl_matchaddr},
  {"matchcidr",       tcl_matchcidr},
  {"matchstr",         tcl_matchstr},
  {"status",             tcl_status},
  {"rfcequal",         tcl_rfcequal},
  {"md5",                   tcl_md5},
  {NULL,                       NULL}
};
