myth/area/imc/
/*
 * IMC2 - an inter-mud communications protocol
 *
 * iced.c: IMC-channel-extensions (ICE) daemon code
 *
 * Copyright (C) 1997 Oliver Jowett <oliver@jowett.manawatu.planet.co.nz>
 *
 * 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 (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

#include "imc.h"
#include "iced.h"

ice_channel *iced_channel_list;
static ice_channel *next_refresh, *next_list;

imc_char_data iced_char =
{
  "ICE", /* name */
  0,     /* invis */
  -1,    /* level */
  0      /* wizi */
};

static void fixactive(ice_channel *c)
{
  char arg[IMC_NAME_LENGTH];
  const char *p;
  char buf[IMC_DATA_LENGTH];
  
  if (c->policy != ICE_PRIVATE)
  {
    imc_strfree(c->active);
    c->active=imc_strdup("");
    return;
  }

  buf[0]=0;
  p=imc_getarg(c->invited, arg, IMC_NAME_LENGTH);
  while (arg[0])
  {
    const char *mud=imc_mudof(arg);

    if (!imc_hasname(buf, mud))
    {
      if (buf[0])
	strcat(buf, " ");

      strcat(buf, mud);
    }

    p=imc_getarg(p, arg, IMC_NAME_LENGTH);
  }

  p=imc_getarg(c->operators, arg, IMC_NAME_LENGTH);
  while (arg[0])
  {
    const char *mud=imc_mudof(arg);

    if (!imc_hasname(buf, mud))
    {
      if (buf[0])
	strcat(buf, " ");

      strcat(buf, mud);
    }

    p=imc_getarg(p, arg, IMC_NAME_LENGTH);
  }

  if (!imc_hasname(buf, imc_mudof(c->owner)))
  {
    if (buf[0])
      strcat(buf, " ");

    strcat(buf, imc_mudof(c->owner));
  }
  
  imc_strfree(c->active);
  c->active=imc_strdup(buf);
}

void iced_save_channels(void)
{
  ice_channel *c;
  FILE *fp;
  char name[IMC_DATA_LENGTH];

  strcpy(name, imc_prefix);
  strcat(name, "iced");

  fp=fopen(name, "w");
  if (!fp)
  {
    imc_logerror("Can't write to %s", name);
    return;
  }
  
  for (c=iced_channel_list; c; c=c->next)
  {
    /* save */
    fprintf(fp,
	    "%s %s %d\n"
	    "op %s\n"
	    "invite %s\n"
	    "exclude %s\n",
	    c->name, c->owner, c->policy,
	    c->operators[0] ? c->operators : "none",
	    c->invited[0] ? c->invited : "none",
	    c->excluded[0] ? c->excluded : "none");
  }

  fclose(fp);
}

void iced_load_channels(void)
{
  FILE *fp;
  char name[IMC_DATA_LENGTH];
  char buf1[IMC_DATA_LENGTH];
  char buf2[IMC_DATA_LENGTH];
  char buf3[IMC_DATA_LENGTH];
  char buf4[IMC_DATA_LENGTH];
  char buf5[IMC_DATA_LENGTH];
  int p;

  strcpy(name, imc_prefix);
  strcat(name, "iced");

  fp=fopen(name, "r");
  if (!fp)
  {
    imc_logerror("Can't open %s", name);
    return;
  }

  while (fscanf(fp,
		"%s %s %d\n"
		"op %[^\n]\n"
		"invite %[^\n]\n"
		"exclude %[^\n]\n", buf1, buf2, &p, buf3, buf4, buf5) == 6)
  {
    ice_channel *c=imc_malloc(sizeof(*c));

    if (!strcmp(buf3, "none"))
      buf3[0]=0;
    if (!strcmp(buf4, "none"))
      buf4[0]=0;
    if (!strcmp(buf5, "none"))
      buf5[0]=0;
    
    c->local=NULL;
    c->name=imc_strdup(buf1);
    c->owner=imc_strdup(buf2);
    c->operators=imc_strdup(buf3);
    c->invited=imc_strdup(buf4);
    c->excluded=imc_strdup(buf5);
    c->active=imc_strdup("");
    c->policy=p;
    
    c->next=iced_channel_list;
    iced_channel_list=c;

    fixactive(c);

    imc_logstring("ICEd: loaded %s (%s) owned by %s",
		  c->name,
		  c->policy == ICE_OPEN ? "open" :
		  c->policy == ICE_CLOSED ? "closed" : "private",
                  c->owner);
  }

  fclose(fp);
}

ice_channel *iced_findchannel(const char *name)
{
  ice_channel *c;

  for (c=iced_channel_list; c; c=c->next)
    if (!strcasecmp(c->name, name))
      return c;

  return NULL;
}

void iced_gannounce(const char *fmt, ...)
{
  char buf[IMC_DATA_LENGTH];
  va_list ap;

  strcpy(buf, "announces: ");
  
  va_start(ap, fmt);
  vsnprintf(buf+strlen(buf), IMC_DATA_LENGTH, fmt, ap);
  va_end(ap);

  imc_send_emote(&iced_char, 15, buf, "*");
}

void iced_privmsg(ice_channel *c, imc_packet *out, const char *exclude)
{
  const char *p;
  char arg[IMC_NAME_LENGTH];
  
  p=imc_getarg(c->active, arg, IMC_NAME_LENGTH);
  while(arg[0])
  {
    if (!exclude || strcasecmp(arg, exclude))
    {
      sprintf(out->to, "*@%s", arg);
      imc_send(out);
    }

    p=imc_getarg(p, arg, IMC_NAME_LENGTH);
  }
}

void iced_announce(ice_channel *c, const char *fmt, ...)
{
  va_list ap;
  char buf[IMC_DATA_LENGTH];

  strcpy(buf, "announces: ");
  
  va_start(ap, fmt);
  vsnprintf(buf+strlen(buf), IMC_DATA_LENGTH-strlen(buf), fmt, ap);
  va_end(ap);

  if (c->policy == ICE_PRIVATE)
  {
    imc_packet out;
    
    strcpy(out.from, "ICE");
    strcpy(out.to, "*");
    strcpy(out.type, "ice-msg-r");

    imc_initdata(&out.data);
    imc_addkey(&out.data, "realfrom", imc_makename("ICE", imc_name));
    imc_addkey(&out.data, "text", buf);
    imc_addkey(&out.data, "channel", c->name);
    imc_addkeyi(&out.data, "emote", 1);

    iced_privmsg(c, &out, NULL);

    imc_freedata(&out.data);
  }
  else
  {
    imc_packet out;

    strcpy(out.from, "ICE");
    strcpy(out.to, "*");
    strcpy(out.type, "ice-msg-b");

    imc_initdata(&out.data);
    imc_addkey(&out.data, "text", buf);
    imc_addkey(&out.data, "channel", c->name);
    imc_addkeyi(&out.data, "emote", 1);

    imc_send(&out);
    imc_freedata(&out.data);
  }
}

int (*iced_recv_chain)(const imc_packet *, int);

/* channel daemon hook */
int iced_recv(const imc_packet *p, int bcast)
{
  /* commands */
  if (!strcasecmp(p->type, "ice-cmd"))
  {
    iced_recv_command(p->from,
		      imc_getkey(&p->data, "channel", ""),
		      imc_getkey(&p->data, "command", ""),
		      imc_getkey(&p->data, "data", ""), 0);
    return 1;
  }
  else if (!strcasecmp(p->type, "ice-msg-p"))
  {
    /* private message to be forwarded */
    iced_recv_msg_p(p->from,
		    imc_getkey(&p->data, "channel", ""),
		    imc_getkey(&p->data, "text", ""),
		    imc_getkeyi(&p->data, "emote", 0));
    return 1;
  }
  else if (!strcasecmp(p->type, "ice-msg-b"))
  {
    /* check for misdirection */
    iced_recv_msg_b(p->from,
		    imc_getkey(&p->data, "channel", ""));
    return 1;
  }
  else if (!strcasecmp(p->type, "ice-refresh"))
  {
    iced_recv_refresh(p->from, imc_getkey(&p->data, "channel", "*"));
    return 1;
  }
  else if (!strcasecmp(p->type, "ice-join-request"))
  {
    iced_recv_join(p->from, imc_getkey(&p->data, "channel", ""));
    return 1;
  }
  else if (!strcasecmp(p->type, "ice-leave-request"))
  {
    iced_recv_leave(p->from, imc_getkey(&p->data, "channel", ""));
    return 1;
  }
  
  if (iced_recv_chain)
    return (*iced_recv_chain)(p, bcast);
  else
    return 0;
}

struct {
  char *name;
  int level;  /* 0=anyone, 1=op only, 2=owner only */
  void (*cmdfn)(ice_channel *c, const char *cname, const char *from, const char *data);
  int needchan;
} iced_cmdtable[] =
{
  { "destroy",    2, iced_destroy,   1 },
  { "policy",     2, iced_policy,    1 },
  { "addop",      2, iced_addop,     1 },
  { "removeop",   2, iced_removeop,  1 },
  { "invite",     1, iced_invite,    1 },
  { "uninvite",   1, iced_uninvite,  1 },
  { "exclude",    1, iced_exclude,   1 },
  { "unexclude",  1, iced_unexclude, 1 },
  { "create",     0, iced_create,    0 },
  { "refresh",    0, iced_refresh,   0 },
  { "list",       0, iced_list,      0 },
  { NULL,         0, NULL              }
};

int iced_getaccess(ice_channel *c, const char *from)
{
  if (!c)
    return 0;
  else if (!strcasecmp(from, c->owner))
    return 2;
  else if (imc_hasname(c->operators, from))
    return 1;
  else
    return 0;
}

void iced_recv_command(const char *from, const char *chan, const char *cmd,
		       const char *data, int override)
{
  ice_channel *c;
  int i;

  if (imc_isignored(from))
  {
    imc_sendignore(from);
    return;
  }
  
  for (i=0; iced_cmdtable[i].name; i++)
    if (!strcasecmp(iced_cmdtable[i].name, cmd))
      break;

  if (!iced_cmdtable[i].name)
  {
    imc_send_tell(&iced_char, from, "Unknown command. Send LIST for a list.", 1);
    return;
  }

  c=iced_findchannel(chan);
  if (!c && iced_cmdtable[i].needchan)
  {
    imc_send_tell(NULL, from, "No such channel. Syntax: icommand <command> <channel> [<data..>]", 1);
    return;
  }

  if (!override && (iced_getaccess(c, from) < iced_cmdtable[i].level))
  {
    imc_send_tell(&iced_char, from, "Insufficient security to do that.", 1);
    return;
  }

  (*iced_cmdtable[i].cmdfn)(c, chan, from, data);
  iced_save_channels();
}

/* list commands */
void iced_list(ice_channel *c, const char *cname, const char *from, const char *data)
{
  char out[IMC_DATA_LENGTH];
  int i;
  int access;

  strcpy(out,
	 "Available commands:\n"
	 "Lvl Ok? Name\n");

  access=iced_getaccess(c, from);
  
  for (i=0; iced_cmdtable[i].name; i++)
  {
    sprintf(out+strlen(out),
	    " %d %s %s\n",
	    iced_cmdtable[i].level,
	    access >= iced_cmdtable[i].level ? "Yes" : "No ",
	    iced_cmdtable[i].name);
  }

  imc_send_tell(&iced_char, from, out, 1);
}

void iced_send_destroy(const char *cname, const char *to)
{
  imc_packet out;

  strcpy(out.from, "ICE");
  strcpy(out.to, to ? to : "*");
  strcpy(out.type, "ice-destroy");
  imc_initdata(&out.data);
  imc_addkey(&out.data, "channel", cname);
  imc_send(&out);
  imc_freedata(&out.data);
}

/* destroy a channel */
void iced_destroy(ice_channel *c, const char *cname, const char *from, const char *data)
{
  ice_channel *p;
  
  if (strcasecmp(data, "destroy"))
  {
    imc_send_tell(&iced_char, from, "Use 'destroy <channel> destroy' to confirm.", 1);
    return;
  }

  /* remove/free the channel from our list */
  
  if (c==iced_channel_list)
    iced_channel_list=c->next;
  else
  {
    for (p=iced_channel_list; p; p=p->next)
      if (p->next == c)
	break;

    if (!p)
      imc_logerror("%s not in channel list?!", c->name);
    else
      p->next=c->next;
  }

  if (c==next_refresh)
    next_refresh=c->next;
  if (c==next_list)
    next_list=c->next;

  imc_logstring("%s destroys channel %s", from, c->name);
  iced_gannounce("Channel %s has been destroyed by %s.", c->name, from);
  
  imc_strfree(c->name);
  imc_strfree(c->owner);
  imc_strfree(c->operators);
  imc_strfree(c->invited);
  imc_strfree(c->excluded);
  imc_strfree(c->active);
  imc_free(c, sizeof(*c));

  imc_send_tell(&iced_char, from, "Done.", 1);

  /* send destroy notification */
  iced_send_destroy(c->name, NULL);
}

/* set channel policy */
void iced_policy(ice_channel *c, const char *cname, const char *from, const char *data)
{
  if (!strcasecmp(data, "open"))
  {
    c->policy=ICE_OPEN;
    iced_announce(c, "Channel policy is now: open.");
    iced_gannounce("%s is now policy: open.", c->name);
    iced_update(c, NULL);
  }
  else if (!strcasecmp(data, "closed"))
  {
    c->policy=ICE_CLOSED;
    iced_announce(c, "Channel policy is now: closed.");
    iced_gannounce("%s is now policy: closed.", c->name);
    iced_update(c, NULL);
  }
  else if (!strcasecmp(data, "private"))
  {
    c->policy=ICE_PRIVATE;
    iced_announce(c, "Channel policy is now: private.");
    iced_gannounce("%s is now policy: private.", c->name);
    iced_update(c, NULL);
  }
  else
    imc_send_tell(&iced_char, from, "Syntax: <channel> policy [open|closed|private]", 1);
}

/* add operator */
void iced_addop(ice_channel *c, const char *cname, const char *from, const char *data)
{
  char buf[1000];
  char arg[IMC_NAME_LENGTH];

  if (!data[0])
  {
    imc_send_tell(&iced_char, from, "Syntax: addop <channel> <user@mud>", 1);
    return;
  }
  
  if (imc_hasname(c->operators, data))
  {
    imc_send_tell(&iced_char, from, "They are already an operator.", 1);
    return;
  }

  imc_getarg(data, arg, IMC_NAME_LENGTH);

  if (!strchr(arg, '@'))
  {
    imc_send_tell(&iced_char, from, "Need a full user@mud name to add.", 1);
    return;
  }
  
  imc_addname(&c->operators, arg);

  sprintf(buf, "%s is now an operator of %s.", arg, c->name);
  imc_send_tell(&iced_char, from, buf, 1);

  iced_announce(c, "%s is now an operator.", arg);
  iced_update(c, NULL);
}

/* remove operator */
void iced_removeop(ice_channel *c, const char *cname, const char *from, const char *data)
{
  char buf[1000];
  char arg[IMC_NAME_LENGTH];
  
  imc_getarg(data, arg, IMC_NAME_LENGTH);

  if (!arg[0])
  {
    imc_send_tell(&iced_char, from, "Syntax: removeop <channel> <user@mud>", 1);
    return;
  }
  
  if (!imc_hasname(c->operators, arg))
  {
    imc_send_tell(&iced_char, from, "They are not an operator.", 1);
    return;
  }

  imc_removename(&c->operators, arg);

  sprintf(buf, "%s is no longer an operator of %s.", arg, c->name);
  imc_send_tell(&iced_char, from, buf, 1);

  iced_announce(c, "%s is no longer an operator.", arg);
  iced_update(c, NULL);
}

/* invite mud or player */
void iced_invite(ice_channel *c, const char *cname, const char *from, const char *data)
{
  char buf[1000];
  char arg[IMC_NAME_LENGTH];
  
  imc_getarg(data, arg, IMC_NAME_LENGTH);

  if (!arg[0])
  {
    imc_send_tell(&iced_char, from, "Syntax: invite <channel> <user@mud>", 1);
    return;
  }
  
  if (imc_hasname(c->invited, arg))
  {
    imc_send_tell(&iced_char, from, "They are already on the invite list.", 1);
    return;
  }

  imc_addname(&c->invited, arg);

  sprintf(buf, "%s is now invited to %s.", arg, c->name);
  imc_send_tell(&iced_char, from, buf, 1);

  iced_announce(c, "%s invites %s.", from, arg);
  iced_update(c, NULL);
}

/* uninvite mud or player */
void iced_uninvite(ice_channel *c, const char *cname, const char *from, const char *data)
{
  char buf[1000];
  char arg[IMC_NAME_LENGTH];
  
  imc_getarg(data, arg, IMC_NAME_LENGTH);

  if (!arg[0])
  {
    imc_send_tell(&iced_char, from, "Syntax: uninvite <channel> <user@mud>", 1);
    return;
  }
  
  if (!imc_hasname(c->invited, arg))
  {
    imc_send_tell(&iced_char, from, "They are not currently on the invite list.", 1);
    return;
  }

  imc_removename(&c->invited, arg);

  sprintf(buf, "%s is no longer invited on %s.", arg, c->name);
  imc_send_tell(&iced_char, from, buf, 1);

  iced_announce(c, "%s uninvites %s.", from, arg);
  iced_update(c, NULL);
}

/* exclude mud or player */
void iced_exclude(ice_channel *c, const char *cname, const char *from, const char *data)
{
  char buf[1000];
  char arg[IMC_NAME_LENGTH];
  
  imc_getarg(data, arg, IMC_NAME_LENGTH);

  if (!arg[0])
  {
    imc_send_tell(&iced_char, from, "Syntax: exclude <channel> <user@mud>", 1);
    return;
  }
  
  if (imc_hasname(c->excluded, arg))
  {
    imc_send_tell(&iced_char, from, "They are already on the exclude list.", 1);
    return;
  }

  imc_addname(&c->excluded, arg);

  sprintf(buf, "%s is now excluded from %s.", arg, c->name);
  imc_send_tell(&iced_char, from, buf, 1);

  iced_announce(c, "%s excludes %s.", from, arg);
  iced_update(c, NULL);
}

/* unexclude mud or player */
void iced_unexclude(ice_channel *c, const char *cname, const char *from, const char *data)
{
  char buf[1000];
  char arg[IMC_NAME_LENGTH];
  
  imc_getarg(data, arg, IMC_NAME_LENGTH);

  if (!arg[0])
  {
    imc_send_tell(&iced_char, from, "Syntax: unexclude <channel> <user@mud>", 1);
    return;
  }
  
  if (!imc_hasname(c->excluded, arg))
  {
    imc_send_tell(&iced_char, from, "They are not on the exclude list.", 1);
    return;
  }

  imc_removename(&c->excluded, arg);

  sprintf(buf, "%s is no longer excluded from %s.", arg, c->name);
  imc_send_tell(&iced_char, from, buf, 1);

  iced_announce(c, "%s unexcludes %s.", from, arg);
  iced_update(c, NULL);
}

/* create a channel */
void iced_create(ice_channel *c, const char *cname, const char *from, const char *data)
{
  ice_channel *p;

  if (!imc_hasname(ICED_CREATORS, "*") &&
      !imc_hasname(ICED_CREATORS, from) &&
      !imc_hasname(ICED_CREATORS, imc_mudof(from)))
  {
    imc_send_tell(&iced_char, from, "You don't have permission to create channels here.", 1);
    return;
  }

  if (!cname[0])
  {
    char buf[IMC_DATA_LENGTH];
    sprintf(buf, "Syntax: create %s:<channel name>", imc_name);
    imc_send_tell(&iced_char, from, buf, 1);
    return;
  }
  
  if (!strchr(cname, ':') ||
      strcasecmp(ice_mudof(cname), imc_name))
  {
    char buf[IMC_DATA_LENGTH];
    sprintf(buf, "Channels created here must begin with %s:", imc_name);
    imc_send_tell(&iced_char, from, buf, 1);
    return;
  }
  
  if (c)
  {
    imc_send_tell(&iced_char, from, "A channel by that name already exists.", 1);
    return;
  }

  p=imc_malloc(sizeof(*p));
  p->name=imc_strdup(cname);
  p->owner=imc_strdup(from);
  p->operators=imc_strdup("");
  p->invited=imc_strdup("");
  p->excluded=imc_strdup("");
  p->active=imc_strdup("");
  p->policy=ICE_CLOSED;

  /* start refreshes as needed */
  if (!iced_channel_list)
    imc_add_event(ICED_REFRESH_TIME, ev_iced_refresh, NULL, 1);
  
  p->next=iced_channel_list;
  iced_channel_list=p;

  iced_update(p, NULL);

  imc_logstring("%s creates channel %s", from, p->name);
  iced_gannounce("Channel %s created by %s.", p->name, from);
  imc_send_tell(&iced_char, from, "Channel created.", 1);
}

/* refresh a channel */
void iced_refresh(ice_channel *c, const char *cname, const char *from, const char *data)
{
  if (!c)
  {
    if (!strcmp(cname, "*"))
    {
      if (!next_list)
      {
	next_list=iced_channel_list;
	imc_add_event(5, ev_iced_chanlist, (void *)from, 1);
      }
      else
      {
	imc_send_tell(&iced_char, from, "Refresh already in progress - try again later.", 1);
      }
    }
    else
    {
      iced_send_destroy(cname, imc_mudof(from));
      imc_send_tell(&iced_char, from, "No such channel. Destroy message sent.", 1);
    }

    return;
  }

  iced_update(c, imc_mudof(from));
  imc_send_tell(&iced_char, from, "Channel refreshed.", 1);
}

/* private message - for forwarding */
void iced_recv_msg_p(const char *from, const char *chan, const char *txt,
		     int emote)
{
  ice_channel *c;
  imc_packet out;
  char temp[IMC_NAME_LENGTH];
  
  if (imc_isignored(from))
  {
    imc_sendignore(from);
    return;
  }

  c=iced_findchannel(chan);
  if (!c)
  {
    imc_send_tell(&iced_char, from, "You're trying to talk on a nonexistant channel.", 1);
    iced_send_destroy(chan, imc_mudof(from));
    return;
  }

  if (!ice_audible(c, from))
  {
    imc_send_tell(&iced_char, from, "You're trying to talk on a channel that you don't have access to.", 1);
    iced_update(c, imc_mudof(from));
    return;
  }

  if (c->policy != ICE_PRIVATE)
  {
    imc_send_tell(&iced_char, from, "Misconfiguration, sending PtP message on nonprivate channel. Try again.", 1);
    iced_update(c, imc_mudof(from));
  }

  strcpy(out.from, "ICE");
  strcpy(out.type, "ice-msg-r"); /* redirect */

  imc_initdata(&out.data);
  imc_addkey(&out.data, "realfrom", from);
  imc_addkey(&out.data, "channel", chan);
  imc_addkey(&out.data, "text", txt);
  imc_addkeyi(&out.data, "emote", emote);

  strcpy(temp, imc_mudof(from)); /* since we do several imc_sends */
  iced_privmsg(c, &out, temp);

  imc_freedata(&out.data);
}

/* broadcast message - complain if its a private channel */
void iced_recv_msg_b(const char *from, const char *chan)
{
  ice_channel *c;

  if (!strchr(chan, ':') ||
      strcasecmp(ice_mudof(chan), imc_name))
    return;
  
  c=iced_findchannel(chan);
  if (!c)
  {
    imc_send_tell(&iced_char, from, "You're trying to talk on a nonexistant channel.", 1);
    iced_send_destroy(chan, imc_mudof(from));
    return;
  }

  if (!ice_audible(c, from))
  {
    imc_send_tell(&iced_char, from, "You're trying to talk on a channel that you don't have access to.", 1);
    iced_update(c, imc_mudof(from));
    return;
  }
  
  if (c->policy == ICE_PRIVATE)
  {
    imc_send_tell(&iced_char, from, "Misconfiguration, sending broadcast message on private channel. Try again.", 1);
    iced_update(c, imc_mudof(from));
  }

  /* do nothing otherwise */
}

/* refresh request */
void iced_recv_refresh(const char *from, const char *chan)
{
  if (!strcmp(chan, "*"))
  {
    if (!next_list)
    {
      next_list=iced_channel_list;
      imc_add_event(5, ev_iced_chanlist, (void *)from, 1);
    }
    else
      return; /* try again later */
  }
  else
  {
    ice_channel *c=iced_findchannel(chan);
    if (!c)
    {
      iced_send_destroy(chan, imc_mudof(from));
      return; /* no such channel */
    }

    iced_update(c, imc_mudof(from));
  }
}

void iced_recv_join(const char *from, const char *chan)
{
  ice_channel *c;
  const char *mud;

  c=iced_findchannel(chan);
  if (!c)
    return;
    
  mud=imc_mudof(from);
  
  if (!ice_audible(c, mud))
    return;
    
  if (imc_hasname(c->active, mud))
    return;
    
  imc_addname(&c->active, mud);
}

void iced_recv_leave(const char *from, const char *chan)
{
  ice_channel *c;
  const char *mud;

  c=iced_findchannel(chan);
  if (!c)
    return;
    
  mud=imc_mudof(from);
  
  if (!imc_hasname(c->active, mud))
    return;

  imc_removename(&c->active, mud);
}
    
/* update a channel */
void iced_update(ice_channel *c, const char *to)
{
  imc_packet out;

  fixactive(c);
  
  strcpy(out.from, "ICE");
  strcpy(out.to, to ? to : "*@*");
  strcpy(out.type, "ice-update");

  imc_initdata(&out.data);
  imc_addkey(&out.data, "channel", c->name);
  imc_addkey(&out.data, "owner", c->owner);
  imc_addkey(&out.data, "operators", c->operators);
  imc_addkey(&out.data, "policy", c->policy == ICE_OPEN ? "open" :
			 c->policy == ICE_CLOSED ? "closed" : "private");
  imc_addkey(&out.data, "invited", c->invited);
  imc_addkey(&out.data, "excluded", c->excluded);

  imc_send(&out);
  imc_freedata(&out.data);
}

/* spam out all channel updates at once */
void ev_iced_chanlist(void *data)
{
  char *to=data;

  if (!next_list)
    return;

  iced_update(next_list, to);
  imc_add_event(5, ev_iced_chanlist, data, 1);

  next_list=next_list->next;
}
  
/* generate a channel listing */
void ev_iced_refresh(void *dummy)
{
  imc_add_event(ICED_REFRESH_TIME, ev_iced_refresh, NULL, 1);

  if (imc_active < IA_UP)
    return;
  
  if (!next_refresh)
    next_refresh=iced_channel_list;

  if (!next_refresh)
    return;

  iced_update(next_refresh, NULL);
  next_refresh=next_refresh->next;
}

/* global init */
void iced_init(void)
{
  imc_logstring("ICE daemon starting.");

  iced_recv_chain=imc_recv_hook;
  imc_recv_hook=iced_recv;

  imc_add_event(ICED_REFRESH_TIME, ev_iced_refresh, NULL, 1);

  iced_load_channels();
}