/*
 * misc.c -- handles:
 *   split() maskhost() dumplots() daysago() days() daysdur()
 *   logging things
 *   queueing output for the bot (msg and help)
 *   resync buffers for sharebots
 *   help system
 *   motd display and %var substitution
 *
 * $Id: misc.c,v 1.77 2006-03-28 02:35:50 wcc Exp $
 */
/*
 * Copyright (C) 1997 Robey Pointer
 * Copyright (C) 1999 - 2006 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 "main.h"
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include "chan.h"
#include "tandem.h"
#include "modules.h"

#ifdef HAVE_UNAME
#  include <sys/utsname.h>
#endif

#include "stat.h"

extern struct dcc_t *dcc;
extern struct chanset_t *chanset;

extern char helpdir[], version[], origbotname[], botname[], admin[], network[],
            motdfile[], ver[], botnetnick[], bannerfile[], logfile_suffix[],
            textdir[];
extern int  backgrd, con_chan, term_z, use_stderr, dcc_total, keep_all_logs,
            quick_logs, strict_host;

extern time_t now;
extern Tcl_Interp *interp;


int shtime = 1;                 /* Display the time with console output */
log_t *logs = 0;                /* Logfiles */
int max_logs = 5;               /* Current maximum log files */
int max_logsize = 0;            /* Maximum logfile size, 0 for no limit */
int raw_log = 0;                /* Disply output to server to LOG_SERVEROUT */

int conmask = LOG_MODES | LOG_CMDS | LOG_MISC; /* Console mask */

struct help_list_t {
  struct help_list_t *next;
  char *name;
  int type;
};

static struct help_ref {
  char *name;
  struct help_list_t *first;
  struct help_ref *next;
} *help_list = NULL;


/* Expected memory usage
 */
int expmem_misc()
{
  struct help_ref *current;
  struct help_list_t *item;
  int tot = 0;

  for (current = help_list; current; current = current->next) {
    tot += sizeof(struct help_ref) + strlen(current->name) + 1;

    for (item = current->first; item; item = item->next)
      tot += sizeof(struct help_list_t) + strlen(item->name) + 1;
  }
  return tot + (max_logs * sizeof(log_t));
}

void init_misc()
{
  static int last = 0;

  if (max_logs < 1)
    max_logs = 1;
  if (logs)
    logs = nrealloc(logs, max_logs * sizeof(log_t));
  else
    logs = nmalloc(max_logs * sizeof(log_t));
  for (; last < max_logs; last++) {
    logs[last].filename = logs[last].chname = NULL;
    logs[last].mask = 0;
    logs[last].f = NULL;
    /* Added by cybah  */
    logs[last].szlast[0] = 0;
    logs[last].repeats = 0;
    /* Added by rtc  */
    logs[last].flags = 0;
  }
}


/*
 *    Misc functions
 */

/* low-level stuff for other modules
 */
int is_file(const char *s)
{
  struct stat ss;
  int i = stat(s, &ss);

  if (i < 0)
    return 0;
  if ((ss.st_mode & S_IFREG) || (ss.st_mode & S_IFLNK))
    return 1;
  return 0;
}

/*  This implementation wont overrun dst - 'max' is the max bytes that dst
 *  can be, including the null terminator. So if 'dst' is a 128 byte buffer,
 *  pass 128 as 'max'. The function will _always_ null-terminate 'dst'.
 *
 *  Returns: The number of characters appended to 'dst'.
 *
 *  Usage example:
 *
 *    char buf[128];
 *    size_t bufsize = sizeof(buf);
 *
 *    buf[0] = 0, bufsize--;
 *
 *    while (blah && bufsize) {
 *      bufsize -= egg_strcatn(buf, <some-long-string>, sizeof(buf));
 *    }
 *
 *  <Cybah>
 */
int egg_strcatn(char *dst, const char *src, size_t max)
{
  size_t tmpmax = 0;

  /* find end of 'dst' */
  while (*dst && max > 0) {
    dst++;
    max--;
  }

  /*    Store 'max', so we can use it to workout how many characters were
   *  written later on.
   */
  tmpmax = max;

  /* copy upto, but not including the null terminator */
  while (*src && max > 1) {
    *dst++ = *src++;
    max--;
  }

  /* null-terminate the buffer */
  *dst = 0;

  /*    Don't include the terminating null in our count, as it will cumulate
   *  in loops - causing a headache for the caller.
   */
  return tmpmax - max;
}

int my_strcpy(register char *a, register char *b)
{
  register char *c = b;

  while (*b)
    *a++ = *b++;
  *a = *b;
  return b - c;
}

/* Split first word off of rest and put it in first
 */
void splitc(char *first, char *rest, char divider)
{
  char *p = strchr(rest, divider);

  if (p == NULL) {
    if (first != rest && first)
      first[0] = 0;
    return;
  }
  *p = 0;
  if (first != NULL)
    strcpy(first, rest);
  if (first != rest)
    /*    In most circumstances, strcpy with src and dst being the same buffer
     *  can produce undefined results. We're safe here, as the src is
     *  guaranteed to be at least 2 bytes higher in memory than dest. <Cybah>
     */
    strcpy(rest, p + 1);
}

/*    As above, but lets you specify the 'max' number of bytes (EXCLUDING the
 * terminating null).
 *
 * Example of use:
 *
 * char buf[HANDLEN + 1];
 *
 * splitcn(buf, input, "@", HANDLEN);
 *
 * <Cybah>
 */
void splitcn(char *first, char *rest, char divider, size_t max)
{
  char *p = strchr(rest, divider);

  if (p == NULL) {
    if (first != rest && first)
      first[0] = 0;
    return;
  }
  *p = 0;
  if (first != NULL)
    strncpyz(first, rest, max);
  if (first != rest)
    /*    In most circumstances, strcpy with src and dst being the same buffer
     *  can produce undefined results. We're safe here, as the src is
     *  guaranteed to be at least 2 bytes higher in memory than dest. <Cybah>
     */
    strcpy(rest, p + 1);
}

char *splitnick(char **blah)
{
  char *p = strchr(*blah, '!'), *q = *blah;

  if (p) {
    *p = 0;
    *blah = p + 1;
    return q;
  }
  return "";
}

void remove_crlf(char **line)
{
  char *p;

  p = strchr(*line, '\n');
  if (p != NULL)
    *p = 0;
  p = strchr(*line, '\r');
  if (p != NULL)
    *p = 0;
}

char *newsplit(char **rest)
{
  register char *o, *r;

  if (!rest)
    return *rest = "";
  o = *rest;
  while (*o == ' ')
    o++;
  r = o;
  while (*o && (*o != ' '))
    o++;
  if (*o)
    *o++ = 0;
  *rest = o;
  return r;
}

/* Convert "abc!user@a.b.host" into "*!user@*.b.host"
 * or "abc!user@1.2.3.4" into "*!user@1.2.3.*"
 * or "abc!user@0:0:0:0:0:ffff:1.2.3.4" into "*!user@0:0:0:0:0:ffff:1.2.3.*"
 * or "abc!user@3ffe:604:2:b02e:6174:7265:6964:6573" into
 *    "*!user@3ffe:604:2:b02e:6174:7265:6964:*"
 */
void _maskhost(const char *s, char *nw, int host)
{
  register const char *p, *q, *e, *f;
  int i;

  *nw++ = '*';
  *nw++ = '!';
  p = (q = strchr(s, '!')) ? q + 1 : s;
  /* Strip of any nick, if a username is found, use last 8 chars */
  if ((q = strchr(p, '@'))) {
    int fl = 0;

    if ((q - p) > 9) {
      nw[0] = '*';
      p = q - 7;
      i = 1;
    } else
      i = 0;
    while (*p != '@') {
      if (!fl && strchr("~+-^=", *p)) {
        if (strict_host)
          nw[i] = '?';
        else if (!host)
          nw[i] = '*';
        else
          i--;
      } else
        nw[i] = *p;
      fl++;
      p++;
      i++;
    }
    nw[i++] = '@';
    q++;
  } else {
    nw[0] = '*';
    nw[1] = '@';
    i = 2;
    q = s;
  }
  nw += i;
  e = NULL;
  /* Now q points to the hostname, i point to where to put the mask */
  if ((!(p = strchr(q, '.')) || !(e = strchr(p + 1, '.'))) && !strchr(q, ':'))
    /* TLD or 2 part host */
    strcpy(nw, q);
  else {
    if (e == NULL) {            /* IPv6 address?                */
      const char *mask_str;

      f = strrchr(q, ':');
      if (strchr(f, '.')) {     /* IPv4 wrapped in an IPv6?     */
        f = strrchr(f, '.');
        mask_str = ".*";
      } else                      /* ... no, true IPv6.               */
        mask_str = ":*";
      strncpy(nw, q, f - q);
      /* No need to nw[f-q] = 0 here, as the strcpy below will
       * terminate the string for us.
       */
      nw += (f - q);
      strcpy(nw, mask_str);
    } else {
      for (f = e; *f; f++);
      f--;
      if (*f >= '0' && *f <= '9') {     /* Numeric IP address */
        while (*f != '.')
          f--;
        strncpy(nw, q, f - q);
        /* No need to nw[f-q] = 0 here, as the strcpy below will
         * terminate the string for us.
         */
        nw += (f - q);
        strcpy(nw, ".*");
      } else {                    /* Normal host >= 3 parts */
        /*    a.b.c  -> *.b.c
         *    a.b.c.d ->  *.b.c.d if tld is a country (2 chars)
         *             OR   *.c.d if tld is com/edu/etc (3 chars)
         *    a.b.c.d.e -> *.c.d.e   etc
         */
        const char *x = strchr(e + 1, '.');

        if (!x)
          x = p;
        else if (strchr(x + 1, '.'))
          x = e;
        else if (strlen(x) == 3)
          x = p;
        else
          x = e;
        sprintf(nw, "*%s", x);
      }
    }
  }
}

/* Dump a potentially super-long string of text.
 */
void dumplots(int idx, const char *prefix, char *data)
{
  char *p = data, *q, *n, c;
  const int max_data_len = 500 - strlen(prefix);

  if (!*data) {
    dprintf(idx, "%s\n", prefix);
    return;
  }
  while (strlen(p) > max_data_len) {
    q = p + max_data_len;
    /* Search for embedded linefeed first */
    n = strchr(p, '\n');
    if (n && n < q) {
      /* Great! dump that first line then start over */
      *n = 0;
      dprintf(idx, "%s%s\n", prefix, p);
      *n = '\n';
      p = n + 1;
    } else {
      /* Search backwards for the last space */
      while (*q != ' ' && q != p)
        q--;
      if (q == p)
        q = p + max_data_len;
      c = *q;
      *q = 0;
      dprintf(idx, "%s%s\n", prefix, p);
      *q = c;
      p = q;
      if (c == ' ')
        p++;
    }
  }
  /* Last trailing bit: split by linefeeds if possible */
  n = strchr(p, '\n');
  while (n) {
    *n = 0;
    dprintf(idx, "%s%s\n", prefix, p);
    *n = '\n';
    p = n + 1;
    n = strchr(p, '\n');
  }
  if (*p)
    dprintf(idx, "%s%s\n", prefix, p);  /* Last trailing bit */
}

/* Convert an interval (in seconds) to one of:
 * "19 days ago", "1 day ago", "18:12"
 */
void daysago(time_t now, time_t then, char *out)
{
  if (now - then > 86400) {
    int days = (now - then) / 86400;

    sprintf(out, "%d day%s ago", days, (days == 1) ? "" : "s");
    return;
  }
  egg_strftime(out, 6, "%H:%M", localtime(&then));
}

/* Convert an interval (in seconds) to one of:
 * "in 19 days", "in 1 day", "at 18:12"
 */
void days(time_t now, time_t then, char *out)
{
  if (now - then > 86400) {
    int days = (now - then) / 86400;

    sprintf(out, "in %d day%s", days, (days == 1) ? "" : "s");
    return;
  }
  egg_strftime(out, 9, "at %H:%M", localtime(&now));
}

/* Convert an interval (in seconds) to one of:
 * "for 19 days", "for 1 day", "for 09:10"
 */
void daysdur(time_t now, time_t then, char *out)
{
  char s[81];
  int hrs, mins;

  if (now - then > 86400) {
    int days = (now - then) / 86400;

    sprintf(out, "for %d day%s", days, (days == 1) ? "" : "s");
    return;
  }
  strcpy(out, "for ");
  now -= then;
  hrs = (int) (now / 3600);
  mins = (int) ((now - (hrs * 3600)) / 60);
  sprintf(s, "%02d:%02d", hrs, mins);
  strcat(out, s);
}


/*
 *    Logging functions
 */

/* Log something
 * putlog(level,channel_name,format,...);
 */
void putlog EGG_VARARGS_DEF(int, arg1)
{
  int i, type, tsl = 0;
  char *format, *chname, s[LOGLINELEN], s1[256], *out, ct[81], *s2, stamp[34];
  va_list va;
  time_t now2 = time(NULL);
  struct tm *t = localtime(&now2);

  type = EGG_VARARGS_START(int, arg1, va);
  chname = va_arg(va, char *);
  format = va_arg(va, char *);

  /* Create the timestamp */
  t = localtime(&now2);
  if (shtime) {
    egg_strftime(stamp, sizeof(stamp) - 2, LOG_TS, t);
    strcat(stamp, " ");
    tsl = strlen(stamp);
  }
  else
    *stamp = '\0';

  /* Format log entry at offset 'tsl,' then i can prepend the timestamp */
  out = s + tsl;
  /* No need to check if out should be null-terminated here,
   * just do it! <cybah>
   */
  egg_vsnprintf(out, LOGLINEMAX - tsl, format, va);
  out[LOGLINEMAX - tsl] = 0;
  if (keep_all_logs) {
    if (!logfile_suffix[0])
      egg_strftime(ct, 12, ".%d%b%Y", t);
    else {
      egg_strftime(ct, 80, logfile_suffix, t);
      ct[80] = 0;
      s2 = ct;
      /* replace spaces by underscores */
      while (s2[0]) {
        if (s2[0] == ' ')
          s2[0] = '_';
        s2++;
      }
    }
  }
  /* Place the timestamp in the string to be printed */
  if (out[0] && shtime) {
    strncpy(s, stamp, tsl);
    out = s;
  }
  strcat(out, "\n");
  if (!use_stderr) {
    for (i = 0; i < max_logs; i++) {
      if ((logs[i].filename != NULL) && (logs[i].mask & type) &&
          ((chname[0] == '*') || (logs[i].chname[0] == '*') ||
           (!rfc_casecmp(chname, logs[i].chname)))) {
        if (logs[i].f == NULL) {
          /* Open this logfile */
          if (keep_all_logs) {
            egg_snprintf(s1, 256, "%s%s", logs[i].filename, ct);
            logs[i].f = fopen(s1, "a+");
          } else
            logs[i].f = fopen(logs[i].filename, "a+");
        }
        if (logs[i].f != NULL) {
          /* Check if this is the same as the last line added to
           * the log. <cybah>
           */
          if (!egg_strcasecmp(out + tsl, logs[i].szlast))
            /* It is a repeat, so increment repeats */
            logs[i].repeats++;
          else {
            /* Not a repeat, check if there were any repeat
             * lines previously...
             */
            if (logs[i].repeats > 0) {
              /* Yep.. so display 'last message repeated x times'
               * then reset repeats. We want the current time here,
               * so put that in the file first.
               */
              fprintf(logs[i].f, stamp);
              fprintf(logs[i].f, MISC_LOGREPEAT, logs[i].repeats);
              logs[i].repeats = 0;
              /* No need to reset logs[i].szlast here
               * because we update it later on...
               */
            }
            fputs(out, logs[i].f);
            strncpyz(logs[i].szlast, out + tsl, LOGLINEMAX);
          }
        }
      }
    }
  }
  for (i = 0; i < dcc_total; i++) {
    if ((dcc[i].type == &DCC_CHAT) && (dcc[i].u.chat->con_flags & type)) {
      if ((chname[0] == '*') || (dcc[i].u.chat->con_chan[0] == '*') ||
          !rfc_casecmp(chname, dcc[i].u.chat->con_chan)) {
        dprintf(i, "%s", out);
      }
    }
  }
  if (!backgrd && !con_chan && !term_z)
    dprintf(DP_STDOUT, "%s", out);
  else if ((type & LOG_MISC) && use_stderr) {
    if (shtime)
      out += tsl;
    dprintf(DP_STDERR, "%s", s);
  }
  va_end(va);
}

/* Called as soon as the logfile suffix changes. All logs are closed
 * and the new suffix is stored in `logfile_suffix'.
 */
void logsuffix_change(char *s)
{
  int i;
  char *s2 = logfile_suffix;

  /* If the suffix didn't really change, ignore. It's probably a rehash. */
  if (s && s2 && !strcmp(s, s2))
    return;

  debug0("Logfile suffix changed. Closing all open logs.");
  strcpy(logfile_suffix, s);
  while (s2[0]) {
    if (s2[0] == ' ')
      s2[0] = '_';
    s2++;
  }
  for (i = 0; i < max_logs; i++) {
    if (logs[i].f) {
      fflush(logs[i].f);
      fclose(logs[i].f);
      logs[i].f = NULL;
    }
  }
}

void check_logsize()
{
  struct stat ss;
  int i;

/* int x=1; */
  char buf[1024];               /* Should be plenty */

  if (!keep_all_logs && max_logsize > 0) {
    for (i = 0; i < max_logs; i++) {
      if (logs[i].filename) {
        if (stat(logs[i].filename, &ss) != 0) {
          break;
        }
        if ((ss.st_size >> 10) > max_logsize) {
          if (logs[i].f) {
            /* write to the log before closing it huh.. */
            putlog(LOG_MISC, "*", MISC_CLOGS, logs[i].filename, ss.st_size);
            fflush(logs[i].f);
            fclose(logs[i].f);
            logs[i].f = NULL;
          }

          egg_snprintf(buf, sizeof buf, "%s.yesterday", logs[i].filename);
          buf[1023] = 0;
          unlink(buf);
          movefile(logs[i].filename, buf);
        }
      }
    }
  }
}

/* Flush the logfiles to disk
 */
void flushlogs()
{
  int i;

  /* Logs may not be initialised yet. */
  if (!logs)
    return;

  /* Now also checks to see if there's a repeat message and
   * displays the 'last message repeated...' stuff too <cybah>
   */
  for (i = 0; i < max_logs; i++) {
    if (logs[i].f != NULL) {
      if ((logs[i].repeats > 0) && quick_logs) {
        /* Repeat.. if quicklogs used then display 'last message
         * repeated x times' and reset repeats.
         */
        char stamp[33];

        egg_strftime(stamp, sizeof(stamp) - 1, LOG_TS, localtime(&now));
        fprintf(logs[i].f, "%s ", stamp);
        fprintf(logs[i].f, MISC_LOGREPEAT, logs[i].repeats);
        /* Reset repeats */
        logs[i].repeats = 0;
      }
      fflush(logs[i].f);
    }
  }
}


/*
 *     String substitution functions
 */

static int cols = 0;
static int colsofar = 0;
static int blind = 0;
static int subwidth = 70;
static char *colstr = NULL;


/* Add string to colstr
 */
static void subst_addcol(char *s, char *newcol)
{
  char *p, *q;
  int i, colwidth;

  if ((newcol[0]) && (newcol[0] != '\377'))
    colsofar++;
  colstr = nrealloc(colstr, strlen(colstr) + strlen(newcol) +
                    (colstr[0] ? 2 : 1));
  if ((newcol[0]) && (newcol[0] != '\377')) {
    if (colstr[0])
      strcat(colstr, "\377");
    strcat(colstr, newcol);
  }
  if ((colsofar == cols) || ((newcol[0] == '\377') && (colstr[0]))) {
    colsofar = 0;
    strcpy(s, "     ");
    colwidth = (subwidth - 5) / cols;
    q = colstr;
    p = strchr(colstr, '\377');
    while (p != NULL) {
      *p = 0;
      strcat(s, q);
      for (i = strlen(q); i < colwidth; i++)
        strcat(s, " ");
      q = p + 1;
      p = strchr(q, '\377');
    }
    strcat(s, q);
    nfree(colstr);
    colstr = nmalloc(1);
    colstr[0] = 0;
  }
}

/* Substitute %x codes in help files
 *
 * %B = bot nickname
 * %V = version
 * %C = list of channels i monitor
 * %E = eggdrop banner
 * %A = admin line
 * %n = network name
 * %T = current time ("14:15")
 * %N = user's nickname
 * %U = display system name if possible
 * %{+xy}     require flags to read this section
 * %{-}       turn of required flag matching only
 * %{center}  center this line
 * %{cols=N}  start of columnated section (indented)
 * %{help=TOPIC} start a section for a particular command
 * %{end}     end of section
 */
#define HELP_BUF_LEN 256
#define HELP_BOLD  1
#define HELP_REV   2
#define HELP_UNDER 4
#define HELP_FLASH 8

void help_subst(char *s, char *nick, struct flag_record *flags,
                int isdcc, char *topic)
{
  char xx[HELP_BUF_LEN + 1], sub[161], *current, *q, chr, *writeidx,
       *readidx, *towrite;
  struct chanset_t *chan;
  int i, j, center = 0;
  static int help_flags;

#ifdef HAVE_UNAME
  struct utsname uname_info;
#endif

  if (s == NULL) {
    /* Used to reset substitutions */
    blind = 0;
    cols = 0;
    subwidth = 70;
    if (colstr != NULL) {
      nfree(colstr);
      colstr = NULL;
    }
    help_flags = isdcc;
    return;
  }
  strncpyz(xx, s, sizeof xx);
  readidx = xx;
  writeidx = s;
  current = strchr(readidx, '%');
  while (current) {
    /* Are we about to copy a chuck to the end of the buffer?
     * if so return
     */
    if ((writeidx + (current - readidx)) >= (s + HELP_BUF_LEN)) {
      strncpy(writeidx, readidx, (s + HELP_BUF_LEN) - writeidx);
      s[HELP_BUF_LEN] = 0;
      return;
    }
    chr = *(current + 1);
    *current = 0;
    if (!blind)
      writeidx += my_strcpy(writeidx, readidx);
    towrite = NULL;
    switch (chr) {
    case 'b':
      if (glob_hilite(*flags)) {
        if (help_flags & HELP_IRC) {
          towrite = "\002";
        } else if (help_flags & HELP_BOLD) {
          help_flags &= ~HELP_BOLD;
          towrite = "\033[0m";
        } else {
          help_flags |= HELP_BOLD;
          towrite = "\033[1m";
        }
      }
      break;
    case 'v':
      if (glob_hilite(*flags)) {
        if (help_flags & HELP_IRC) {
          towrite = "\026";
        } else if (help_flags & HELP_REV) {
          help_flags &= ~HELP_REV;
          towrite = "\033[0m";
        } else {
          help_flags |= HELP_REV;
          towrite = "\033[7m";
        }
      }
      break;
    case '_':
      if (glob_hilite(*flags)) {
        if (help_flags & HELP_IRC) {
          towrite = "\037";
        } else if (help_flags & HELP_UNDER) {
          help_flags &= ~HELP_UNDER;
          towrite = "\033[0m";
        } else {
          help_flags |= HELP_UNDER;
          towrite = "\033[4m";
        }
      }
      break;
    case 'f':
      if (glob_hilite(*flags)) {
        if (help_flags & HELP_FLASH) {
          if (help_flags & HELP_IRC)
            towrite = "\002\037";
          else
            towrite = "\033[0m";
          help_flags &= ~HELP_FLASH;
        } else {
          help_flags |= HELP_FLASH;
          if (help_flags & HELP_IRC)
            towrite = "\037\002";
          else
            towrite = "\033[5m";
        }
      }
      break;
    case 'U':
#ifdef HAVE_UNAME
      if (uname(&uname_info) >= 0) {
        egg_snprintf(sub, sizeof sub, "%s %s", uname_info.sysname,
                     uname_info.release);
        towrite = sub;
      } else
#endif
        towrite = "*UNKNOWN*";
      break;
    case 'B':
      towrite = (isdcc ? botnetnick : botname);
      break;
    case 'V':
      towrite = ver;
      break;
    case 'E':
      towrite = version;
      break;
    case 'A':
      towrite = admin;
      break;
    case 'n':
      towrite = network;
      break;
    case 'T':
      egg_strftime(sub, 6, "%H:%M", localtime(&now));
      towrite = sub;
      break;
    case 'N':
      towrite = strchr(nick, ':');
      if (towrite)
        towrite++;
      else
        towrite = nick;
      break;
    case 'C':
      if (!blind)
        for (chan = chanset; chan; chan = chan->next) {
          if ((strlen(chan->dname) + writeidx + 2) >= (s + HELP_BUF_LEN)) {
            strncpy(writeidx, chan->dname, (s + HELP_BUF_LEN) - writeidx);
            s[HELP_BUF_LEN] = 0;
            return;
          }
          writeidx += my_strcpy(writeidx, chan->dname);
          if (chan->next) {
            *writeidx++ = ',';
            *writeidx++ = ' ';
          }
        }
      break;
    case '{':
      q = current;
      current++;
      while ((*current != '}') && (*current))
        current++;
      if (*current) {
        *current = 0;
        current--;
        q += 2;
        /* Now q is the string and p is where the rest of the fcn expects */
        if (!strncmp(q, "help=", 5)) {
          if (topic && egg_strcasecmp(q + 5, topic))
            blind |= 2;
          else
            blind &= ~2;
        } else if (!(blind & 2)) {
          if (q[0] == '+') {
            struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };

            break_down_flags(q + 1, &fr, NULL);

            /* We used to check flagrec_ok(), but we can use flagrec_eq()
             * instead because lower flags are automatically added now.
             */
            if (!flagrec_eq(&fr, flags))
              blind |= 1;
            else
              blind &= ~1;
          } else if (q[0] == '-')
            blind &= ~1;
          else if (!egg_strcasecmp(q, "end")) {
            blind &= ~1;
            subwidth = 70;
            if (cols) {
              sub[0] = 0;
              subst_addcol(sub, "\377");
              nfree(colstr);
              colstr = NULL;
              cols = 0;
              towrite = sub;
            }
          } else if (!egg_strcasecmp(q, "center"))
            center = 1;
          else if (!strncmp(q, "cols=", 5)) {
            char *r;

            cols = atoi(q + 5);
            colsofar = 0;
            colstr = nmalloc(1);
            colstr[0] = 0;
            r = strchr(q + 5, '/');
            if (r != NULL)
              subwidth = atoi(r + 1);
          }
        }
      } else
        current = q;            /* no } so ignore */
      break;
    default:
      if (!blind) {
        *writeidx++ = chr;
        if (writeidx >= (s + HELP_BUF_LEN)) {
          *writeidx = 0;
          return;
        }
      }
    }
    if (towrite && !blind) {
      if ((writeidx + strlen(towrite)) >= (s + HELP_BUF_LEN)) {
        strncpy(writeidx, towrite, (s + HELP_BUF_LEN) - writeidx);
        s[HELP_BUF_LEN] = 0;
        return;
      }
      writeidx += my_strcpy(writeidx, towrite);
    }
    if (chr) {
      readidx = current + 2;
      current = strchr(readidx, '%');
    } else {
      readidx = current + 1;
      current = NULL;
    }
  }
  if (!blind) {
    i = strlen(readidx);
    if (i && ((writeidx + i) >= (s + HELP_BUF_LEN))) {
      strncpy(writeidx, readidx, (s + HELP_BUF_LEN) - writeidx);
      s[HELP_BUF_LEN] = 0;
      return;
    }
    strcpy(writeidx, readidx);
  } else
    *writeidx = 0;
  if (center) {
    strcpy(xx, s);
    i = 35 - (strlen(xx) / 2);
    if (i > 0) {
      s[0] = 0;
      for (j = 0; j < i; j++)
        s[j] = ' ';
      strcpy(s + i, xx);
    }
  }
  if (cols) {
    strcpy(xx, s);
    s[0] = 0;
    subst_addcol(s, xx);
  }
}

static void scan_help_file(struct help_ref *current, char *filename, int type)
{
  FILE *f;
  char s[HELP_BUF_LEN + 1], *p, *q;
  struct help_list_t *list;

  if (is_file(filename) && (f = fopen(filename, "r"))) {
    while (!feof(f)) {
      fgets(s, HELP_BUF_LEN, f);
      if (!feof(f)) {
        p = s;
        while ((q = strstr(p, "%{help="))) {
          q += 7;
          if ((p = strchr(q, '}'))) {
            *p = 0;
            list = nmalloc(sizeof *list);

            list->name = nmalloc(p - q + 1);
            strcpy(list->name, q);
            list->next = current->first;
            list->type = type;
            current->first = list;
            p++;
          } else
            p = "";
        }
      }
    }
    fclose(f);
  }
}

void add_help_reference(char *file)
{
  char s[1024];
  struct help_ref *current;

  for (current = help_list; current; current = current->next)
    if (!strcmp(current->name, file))
      return;                   /* Already exists, can't re-add :P */
  current = nmalloc(sizeof *current);

  current->name = nmalloc(strlen(file) + 1);
  strcpy(current->name, file);
  current->next = help_list;
  current->first = NULL;
  help_list = current;
  egg_snprintf(s, sizeof s, "%smsg/%s", helpdir, file);
  scan_help_file(current, s, 0);
  egg_snprintf(s, sizeof s, "%s%s", helpdir, file);
  scan_help_file(current, s, 1);
  egg_snprintf(s, sizeof s, "%sset/%s", helpdir, file);
  scan_help_file(current, s, 2);
}

void rem_help_reference(char *file)
{
  struct help_ref *current, *last = NULL;
  struct help_list_t *item;

  for (current = help_list; current; last = current, current = current->next)
    if (!strcmp(current->name, file)) {
      while ((item = current->first)) {
        current->first = item->next;
        nfree(item->name);
        nfree(item);
      }
      nfree(current->name);
      if (last)
        last->next = current->next;
      else
        help_list = current->next;
      nfree(current);
      return;
    }
}

void reload_help_data(void)
{
  struct help_ref *current = help_list, *next;
  struct help_list_t *item;

  help_list = NULL;
  while (current) {
    while ((item = current->first)) {
      current->first = item->next;
      nfree(item->name);
      nfree(item);
    }
    add_help_reference(current->name);
    nfree(current->name);
    next = current->next;
    nfree(current);
    current = next;
  }
}

void debug_help(int idx)
{
  struct help_ref *current;
  struct help_list_t *item;

  for (current = help_list; current; current = current->next) {
    dprintf(idx, "HELP FILE(S): %s\n", current->name);
    for (item = current->first; item; item = item->next) {
      dprintf(idx, "   %s (%s)\n", item->name, (item->type == 0) ? "msg/" :
              (item->type == 1) ? "" : "set/");
    }
  }
}

FILE *resolve_help(int dcc, char *file)
{

  char s[1024];
  FILE *f;
  struct help_ref *current;
  struct help_list_t *item;

  /* Somewhere here goes the eventual substituation */
  if (!(dcc & HELP_TEXT)) {
    for (current = help_list; current; current = current->next)
      for (item = current->first; item; item = item->next)
        if (!strcmp(item->name, file)) {
          if (!item->type && !dcc) {
            egg_snprintf(s, sizeof s, "%smsg/%s", helpdir, current->name);
            if ((f = fopen(s, "r")))
              return f;
          } else if (dcc && item->type) {
            if (item->type == 1)
              egg_snprintf(s, sizeof s, "%s%s", helpdir, current->name);
            else
              egg_snprintf(s, sizeof s, "%sset/%s", helpdir, current->name);
            if ((f = fopen(s, "r")))
              return f;
          }
        }
    /* No match was found, so we better return NULL */
    return NULL;
  }
  /* Since we're not dealing with help files, we should just prepend the filename with textdir */
  simple_sprintf(s, "%s%s", textdir, file);
  if (is_file(s))
    return fopen(s, "r");
  else
    return NULL;
}

void showhelp(char *who, char *file, struct flag_record *flags, int fl)
{
  int lines = 0;
  char s[HELP_BUF_LEN + 1];
  FILE *f = resolve_help(fl, file);

  if (f) {
    help_subst(NULL, NULL, 0, HELP_IRC, NULL);  /* Clear flags */
    while (!feof(f)) {
      fgets(s, HELP_BUF_LEN, f);
      if (!feof(f)) {
        if (s[strlen(s) - 1] == '\n')
          s[strlen(s) - 1] = 0;
        if (!s[0])
          strcpy(s, " ");
        help_subst(s, who, flags, 0, file);
        if ((s[0]) && (strlen(s) > 1)) {
          dprintf(DP_HELP, "NOTICE %s :%s\n", who, s);
          lines++;
        }
      }
    }
    fclose(f);
  }
  if (!lines && !(fl & HELP_TEXT))
    dprintf(DP_HELP, "NOTICE %s :%s\n", who, IRC_NOHELP2);
}

static int display_tellhelp(int idx, char *file, FILE *f,
                            struct flag_record *flags)
{
  char s[HELP_BUF_LEN + 1];
  int lines = 0;

  if (f) {
    help_subst(NULL, NULL, 0,
               (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
    while (!feof(f)) {
      fgets(s, HELP_BUF_LEN, f);
      if (!feof(f)) {
        if (s[strlen(s) - 1] == '\n')
          s[strlen(s) - 1] = 0;
        if (!s[0])
          strcpy(s, " ");
        help_subst(s, dcc[idx].nick, flags, 1, file);
        if (s[0]) {
          dprintf(idx, "%s\n", s);
          lines++;
        }
      }
    }
    fclose(f);
  }
  return lines;
}

void tellhelp(int idx, char *file, struct flag_record *flags, int fl)
{
  int lines = 0;
  FILE *f = resolve_help(HELP_DCC | fl, file);

  if (f)
    lines = display_tellhelp(idx, file, f, flags);
  if (!lines && !(fl & HELP_TEXT))
    dprintf(idx, "%s\n", IRC_NOHELP2);
}

/* Same as tellallhelp, just using wild_match instead of strcmp
 */
void tellwildhelp(int idx, char *match, struct flag_record *flags)
{
  struct help_ref *current;
  struct help_list_t *item;
  FILE *f;
  char s[1024];

  s[0] = '\0';
  for (current = help_list; current; current = current->next)
    for (item = current->first; item; item = item->next)
      if (wild_match(match, item->name) && item->type) {
        if (item->type == 1)
          egg_snprintf(s, sizeof s, "%s%s", helpdir, current->name);
        else
          egg_snprintf(s, sizeof s, "%sset/%s", helpdir, current->name);
        if ((f = fopen(s, "r")))
          display_tellhelp(idx, item->name, f, flags);
      }
  if (!s[0])
    dprintf(idx, "%s\n", IRC_NOHELP2);
}

/* Same as tellwildhelp, just using strcmp instead of wild_match
 */
void tellallhelp(int idx, char *match, struct flag_record *flags)
{
  struct help_ref *current;
  struct help_list_t *item;
  FILE *f;
  char s[1024];

  s[0] = '\0';
  for (current = help_list; current; current = current->next)
    for (item = current->first; item; item = item->next)
      if (!strcmp(match, item->name) && item->type) {

        if (item->type == 1)
          egg_snprintf(s, sizeof s, "%s%s", helpdir, current->name);
        else
          egg_snprintf(s, sizeof s, "%sset/%s", helpdir, current->name);
        if ((f = fopen(s, "r")))
          display_tellhelp(idx, item->name, f, flags);
      }
  if (!s[0])
    dprintf(idx, "%s\n", IRC_NOHELP2);
}

/* Substitute vars in a lang text to dcc chatter
 */
void sub_lang(int idx, char *text)
{
  char s[1024];
  struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };

  get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
  help_subst(NULL, NULL, 0,
             (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
  strncpyz(s, text, sizeof s);
  if (s[strlen(s) - 1] == '\n')
    s[strlen(s) - 1] = 0;
  if (!s[0])
    strcpy(s, " ");
  help_subst(s, dcc[idx].nick, &fr, 1, botnetnick);
  if (s[0])
    dprintf(idx, "%s\n", s);
}

/* This will return a pointer to the first character after the @ in the
 * string given it.  Possibly it's time to think about a regexp library
 * for eggdrop...
 */
char *extracthostname(char *hostmask)
{
  char *p = strrchr(hostmask, '@');

  return p ? p + 1 : "";
}

/* Show motd to dcc chatter
 */
void show_motd(int idx)
{
  FILE *vv;
  char s[1024];
  struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };

  if (!is_file(motdfile))
    return;

  vv = fopen(motdfile, "r");
  if (!vv)
    return;

  get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
  dprintf(idx, "\n");
  /* reset the help_subst variables to their defaults */
  help_subst(NULL, NULL, 0,
             (dcc[idx].status & STAT_TELNET) ? 0 : HELP_IRC, NULL);
  while (!feof(vv)) {
    fgets(s, 120, vv);
    if (!feof(vv)) {
      if (s[strlen(s) - 1] == '\n')
        s[strlen(s) - 1] = 0;
      if (!s[0])
        strcpy(s, " ");
      help_subst(s, dcc[idx].nick, &fr, 1, botnetnick);
      if (s[0])
        dprintf(idx, "%s\n", s);
    }
  }
  fclose(vv);
  dprintf(idx, "\n");
}

/* Show banner to telnet user
 */
void show_banner(int idx)
{
  FILE *vv;
  char s[1024];
  struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };

  if (!is_file(bannerfile))
    return;

  vv = fopen(bannerfile, "r");
  if (!vv)
    return;

  get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
  /* reset the help_subst variables to their defaults */
  help_subst(NULL, NULL, 0, 0, NULL);
  while (!feof(vv)) {
    fgets(s, 120, vv);
    if (!feof(vv)) {
      if (!s[0])
        strcpy(s, " \n");
      help_subst(s, dcc[idx].nick, &fr, 0, botnetnick);
      dprintf(idx, "%s", s);
    }
  }
  fclose(vv);
}

/* Create a string with random letters and digits
 */
void make_rand_str(char *s, int len)
{
  int j;

  for (j = 0; j < len; j++) {
    if (!randint(3))
      s[j] = '0' + randint(10);
    else
      s[j] = 'a' + randint(26);
  }
  s[len] = 0;
}

/* Convert an octal string into a decimal integer value.  If the string
 * is empty or contains non-octal characters, -1 is returned.
 */
int oatoi(const char *octal)
{
  register int i;

  if (!*octal)
    return -1;
  for (i = 0; ((*octal >= '0') && (*octal <= '7')); octal++)
    i = (i * 8) + (*octal - '0');
  if (*octal)
    return -1;
  return i;
}

/* Return an allocated buffer which contains a copy of the string
 * 'str', with all 'div' characters escaped by 'mask'. 'mask'
 * characters are escaped too.
 *
 * Remember to free the returned memory block.
 */
char *str_escape(const char *str, const char div, const char mask)
{
  const int len = strlen(str);
  int buflen = (2 * len), blen = 0;
  char *buf = nmalloc(buflen + 1), *b = buf;
  const char *s;

  if (!buf)
    return NULL;
  for (s = str; *s; s++) {
    /* Resize buffer. */
    if ((buflen - blen) <= 3) {
      buflen = (buflen * 2);
      buf = nrealloc(buf, buflen + 1);
      if (!buf)
        return NULL;
      b = buf + blen;
    }

    if (*s == div || *s == mask) {
      sprintf(b, "%c%02x", mask, *s);
      b += 3;
      blen += 3;
    } else {
      *(b++) = *s;
      blen++;
    }
  }
  *b = 0;
  return buf;
}

/* Search for a certain character 'div' in the string 'str', while
 * ignoring escaped characters prefixed with 'mask'.
 *
 * The string
 *
 *   "\\3a\\5c i am funny \\3a):further text\\5c):oink"
 *
 * as str, '\\' as mask and ':' as div would change the str buffer
 * to
 *
 *   ":\\ i am funny :)"
 *
 * and return a pointer to "further text\\5c):oink".
 *
 * NOTE: If you look carefully, you'll notice that strchr_unescape()
 *       behaves differently than strchr().
 */
char *strchr_unescape(char *str, const char div, register const char esc_char)
{
  char buf[3];
  register char *s, *p;

  buf[2] = 0;
  for (s = p = str; *s; s++, p++) {
    if (*s == esc_char) {       /* Found escape character.              */
      /* Convert code to character. */
      buf[0] = s[1], buf[1] = s[2];
      *p = (unsigned char) strtol(buf, NULL, 16);
      s += 2;
    } else if (*s == div) {
      *p = *s = 0;
      return (s + 1);           /* Found searched for character.        */
    } else
      *p = *s;
  }
  *p = 0;
  return NULL;
}

/* Is every character in a string a digit? */
int str_isdigit(const char *str)
{
  if (!*str)
    return 0;

  for (; *str; ++str) {
    if (!egg_isdigit(*str))
      return 0;
  }
  return 1;
}

/* As strchr_unescape(), but converts the complete string, without
 * searching for a specific delimiter character.
 */
void str_unescape(char *str, register const char esc_char)
{
  (void) strchr_unescape(str, 0, esc_char);
}

/* Kills the bot. s1 is the reason shown to other bots,
 * s2 the reason shown on the partyline. (Sup 25Jul2001)
 */
void kill_bot(char *s1, char *s2)
{
  call_hook(HOOK_DIE);
  chatout("*** %s\n", s1);
  botnet_send_chat(-1, botnetnick, s1);
  botnet_send_bye();
  write_userfile(-1);
  fatal(s2, 0);
}
