myth/area/imc/
/*
 * IMC2 - an inter-mud communications protocol
 *
 * imc-interp.c: packet interpretation code
 *
 * Copyright (C) 1996,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 <sys/types.h>
#include <sys/time.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>

#include "imc.h"

/* rignore'd people */
imc_ignore_data *imc_ignore_list;
/* prefixes for all data files */
char *imc_prefix;

/* called when a keepalive has been received */
void imc_recv_keepalive(const char *from, const char *version, const char *flags)
{
  imc_reminfo *p;

  if (!strcasecmp(from, imc_name))
    return;
  
  /*  this should never fail, imc.c should create an
   *  entry if one doesn't exist (in the path update
   *  code)
   */
  p=imc_find_reminfo(from, 0);
  if (!p)		    /* boggle */
    return;

  if (imc_hasname(flags, "hide"))
    p->hide=1;
  else
    p->hide=0;
  
  /* lower-level code has already updated p->alive */

  if (strcasecmp(version, p->version))    /* remote version has changed? */
  {
    imc_strfree(p->version);              /* if so, update it */
    p->version=imc_strdup(version);
  }

  /* Only routers should ping - and even then, only directly connected muds */
  if (imc_is_router && imc_getinfo(from))
  {
    struct timeval tv;
    gettimeofday(&tv, NULL);

    imc_send_ping(from, tv.tv_sec, tv.tv_usec);
  }
}

/* called when a ping request is received */
void imc_recv_ping(const char *from, int time_s, int time_u, const char *path)
{
  /* ping 'em back */
  imc_send_pingreply(from, time_s, time_u, path);
}

/* called when a ping reply is received */
void imc_recv_pingreply(const char *from, int time_s, int time_u, const char *pathto, const char *pathfrom)
{
  imc_reminfo *p;
  struct timeval tv;

  p = imc_find_reminfo(from, 0);   /* should always exist */
  if (!p)			   /* boggle */
    return;

  gettimeofday(&tv, NULL);      /* grab the exact time now and calc RTT */
  p->ping = (tv.tv_sec - time_s) * 1000 + (tv.tv_usec - time_u) / 1000;

  /* check for pending traceroutes */
  imc_traceroute(p->ping, pathto, pathfrom);
}

/* send a standard 'you are being ignored' rtell */
void imc_sendignore(const char *to)
{
  char buf[IMC_DATA_LENGTH];

  if (strcmp(imc_nameof(to), "*"))
  {
    sprintf(buf, "%s is ignoring you.", imc_name);
    imc_send_tell(NULL, to, buf, 1);
  }
}

/* imc_char_data representation:
 *
 *  Levels are simplified: >0 is a mortal, <0 is an immortal. The 'see' and
 *  'invis' fields are no longer used.
 *
 *  d->wizi  is -1 if the character is invisible to mortals (hidden/invis or
 *           wizi)
 *  d->level is the level of the character (-1=imm, 1=mortal)
 *
 *  also checks rignores for a 'notrust' flag which makes that person a
 *  level 1 mortal for the purposes of wizi visibility checks, etc
 *
 *  Default behavior is now: trusted.
 *  If there's a notrust flag, untrusted. If there's also a trust flag, trusted
 */

/* convert from the char data in 'p' to an internal representation in 'd' */
static void getdata(const imc_packet *p, imc_char_data *d)
{
  int trust=1;

  if (imc_findignore(p->from, IMC_NOTRUST))
    trust=0;
  if (imc_findignore(p->from, IMC_TRUST))
    trust=1;

  strcpy(d->name, p->from);
  d->wizi = trust ? imc_getkeyi(&p->data, "wizi", 0) : 0;
  d->level = trust ? imc_getkeyi(&p->data, "level", 0) : 0;
  d->invis = 0;
}

/* convert back from 'd' to 'p' */
static void setdata(imc_packet *p, const imc_char_data *d)
{
  imc_initdata(&p->data);

  if (!d)
  {
    strcpy(p->from, "*");
    imc_addkeyi(&p->data, "level", -1);
    return;
  }

  strcpy(p->from, d->name);

  if (d->wizi)
    imc_addkeyi(&p->data, "wizi", d->wizi);
  imc_addkeyi(&p->data, "level", d->level);
}

/* handle a packet destined for us, or a broadcast */
void imc_recv(const imc_packet *p)
{
  imc_char_data d;
  int bcast;

  bcast=!strcmp(imc_mudof(p->i.to), "*") ? 1 : 0;
  
  getdata(p, &d);

  /* chat: message to a channel (broadcast) */
  if (!strcasecmp(p->type, "chat") && !imc_isignored(p->from))
    imc_recv_chat(&d, imc_getkeyi(&p->data, "channel", 0),
		  imc_getkey(&p->data, "text", ""));

  /* emote: emote to a channel (broadcast) */
  else if (!strcasecmp(p->type, "emote") && !imc_isignored(p->from))
    imc_recv_emote(&d, imc_getkeyi(&p->data, "channel", 0),
		   imc_getkey(&p->data, "text", ""));

  /* tell: tell a player here something */
  else if (!strcasecmp(p->type, "tell"))
    if (imc_isignored(p->from))
      imc_sendignore(p->from);
    else
      imc_recv_tell(&d, p->to, imc_getkey(&p->data, "text", ""),
		    imc_getkeyi(&p->data, "isreply", 0));

  /* who-reply: receive a who response */
  else if (!strcasecmp(p->type, "who-reply"))
    imc_recv_whoreply(p->to, imc_getkey(&p->data, "text", ""),
		      imc_getkeyi(&p->data, "sequence", -1));

  /* who: receive a who request */
  else if (!strcasecmp(p->type, "who"))
    if (imc_isignored(p->from))
      imc_sendignore(p->from);
    else
      imc_recv_who(&d, imc_getkey(&p->data, "type", "who"));

  /* whois-reply: receive a whois response */
  else if (!strcasecmp(p->type, "whois-reply"))
    imc_recv_whoisreply(p->to, imc_getkey(&p->data, "text", ""));

  /* whois: receive a whois request */
  else if (!strcasecmp(p->type, "whois"))
    imc_recv_whois(&d, p->to);

  /* beep: beep a player */
  else if (!strcasecmp(p->type, "beep"))
    if (imc_isignored(p->from))
      imc_sendignore(p->from);
    else
      imc_recv_beep(&d, p->to);

  /* is-alive: receive a keepalive (broadcast) */
  else if (!strcasecmp(p->type, "is-alive"))
    imc_recv_keepalive(imc_mudof(p->from),
		       imc_getkey(&p->data, "versionid", "unknown"),
		       imc_getkey(&p->data, "flags", ""));

  /* ping: receive a ping request */
  else if (!strcasecmp(p->type, "ping"))
    imc_recv_ping(imc_mudof(p->from), imc_getkeyi(&p->data, "time-s", 0),
		  imc_getkeyi(&p->data, "time-us", 0), p->i.path);

  /* ping-reply: receive a ping reply */
  else if (!strcasecmp(p->type, "ping-reply"))
    imc_recv_pingreply(imc_mudof(p->from), imc_getkeyi(&p->data, "time-s", 0),
		       imc_getkeyi(&p->data, "time-us", 0),
		       imc_getkey(&p->data, "path", NULL), p->i.path);

  /* mail: mail something to a local player */
  else if (!strcasecmp(p->type, "mail"))
    imc_recv_mail(imc_getkey(&p->data, "from", "error@hell"),
		  imc_getkey(&p->data, "to", "error@hell"),
		  imc_getkey(&p->data, "date", "(IMC error: bad date)"),
		  imc_getkey(&p->data, "subject", "no subject"),
		  imc_getkey(&p->data, "id", "bad_id"),
		  imc_getkey(&p->data, "text", ""));

  /* mail-ok: remote confirmed that they got the mail ok */
  else if (!strcasecmp(p->type, "mail-ok"))
    imc_recv_mailok(p->from, imc_getkey(&p->data, "id", "bad_id"));

  /* mail-reject: remote rejected our mail, bounce it */
  else if (!strcasecmp(p->type, "mail-reject"))
    imc_recv_mailrej(p->from, imc_getkey(&p->data, "id", "bad_id"),
		     imc_getkey(&p->data, "reason",
				"(IMC error: no reason supplied"));

  else if (!strcasecmp(p->type, "info-request"))
    imc_recv_inforequest(p->from, imc_getkey(&p->data, "category", ""));

  /* call catch-all fn if present */
  else
  {
    imc_packet out;

    if (imc_recv_hook)
      if ((*imc_recv_hook)(p, bcast))
	return;

    if (bcast || !strcasecmp(p->type, "reject"))
      return;
    
    /* reject packet */
      
    strcpy(out.type, "reject");
    strcpy(out.to, p->from);
    strcpy(out.from, p->to);

    imc_clonedata(&p->data, &out.data);
    imc_addkey(&out.data, "old-type", p->type);

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


/* Commands called by the interface layer */

/* return mud information.
 * yes, this is protocol level, and -required-
 */
void imc_recv_inforequest(const char *from, const char *category)
{
  imc_packet reply;

  strcpy(reply.to, from);
  strcpy(reply.from, "*");

  imc_initdata(&reply.data);

  if (imc_isignored(from))
  {
    strcpy(reply.type, "info-unavailable");
    imc_send(&reply);
  }
  else if (!strcasecmp(category, "site"))
  {
    strcpy(reply.type, "info-reply");
    
    imc_addkey(&reply.data, "name",    imc_siteinfo.name);
    imc_addkey(&reply.data, "host",    imc_siteinfo.host);
    imc_addkey(&reply.data, "email",   imc_siteinfo.email);
    imc_addkey(&reply.data, "imail",   imc_siteinfo.imail);
    imc_addkey(&reply.data, "www",     imc_siteinfo.www);
    imc_addkey(&reply.data, "version", IMC_VERSIONID);
    imc_addkey(&reply.data, "details", imc_siteinfo.details);
    imc_addkey(&reply.data, "flags",   imc_siteinfo.flags);

    imc_send(&reply);
  }
  else
  {
    strcpy(reply.type, "info-unavailable");
    imc_send(&reply);
  }

  imc_freedata(&reply.data);
}

/* send a message out on a channel */
void imc_send_chat(const imc_char_data *from, int channel,
		   const char *argument, const char *to)
{
  imc_packet out;
  char tobuf[IMC_MNAME_LENGTH];

  if (imc_active<IA_UP)
    return;

  setdata(&out, from);

  strcpy(out.type, "chat");
  strcpy(out.to, "*@*");
  imc_addkey(&out.data, "text", argument);
  imc_addkeyi(&out.data, "channel", channel);

  to=imc_getarg(to, tobuf, IMC_MNAME_LENGTH);
  while (tobuf[0])
  {
    if (!strcmp(tobuf, "*") || !strcasecmp(tobuf, imc_name) ||
	imc_find_reminfo(tobuf, 0))
    {
      strcpy(out.to, "*@");
      strcat(out.to, tobuf);
      imc_send(&out);
    }

    to=imc_getarg(to, tobuf, IMC_MNAME_LENGTH);
  }

  imc_freedata(&out.data);
}

/* send an emote out on a channel */
void imc_send_emote(const imc_char_data *from, int channel,
		    const char *argument, const char *to)
{
  imc_packet out;
  char tobuf[IMC_MNAME_LENGTH];

  if (imc_active<IA_UP)
    return;

  setdata(&out, from);

  strcpy(out.type, "emote");
  imc_addkeyi(&out.data, "channel", channel);
  imc_addkey(&out.data, "text", argument);

  to=imc_getarg(to, tobuf, IMC_MNAME_LENGTH);
  while (tobuf[0])
  {
    if (!strcmp(tobuf, "*") || !strcasecmp(tobuf, imc_name) ||
	imc_find_reminfo(tobuf, 0))
    {
      strcpy(out.to, "*@");
      strcat(out.to, tobuf);
      imc_send(&out);
    }

    to=imc_getarg(to, tobuf, IMC_MNAME_LENGTH);
  }

  imc_freedata(&out.data);
}

/* send a tell to a remote player */
void imc_send_tell(const imc_char_data *from, const char *to,
		   const char *argument, int isreply)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  if (!strcmp(imc_mudof(to), "*"))
    return; /* don't let them do this */

  setdata(&out, from);

  imc_sncpy(out.to, to, IMC_NAME_LENGTH);
  strcpy(out.type, "tell");
  imc_addkey(&out.data, "text", argument);
  if (isreply)
    imc_addkeyi(&out.data, "isreply", isreply);

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

/* send a who-request to a remote mud */
void imc_send_who(const imc_char_data *from, const char *to, const char *type)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  if (!strcmp(imc_mudof(to), "*"))
    return; /* don't let them do this */

  setdata(&out, from);

  sprintf(out.to, "*@%s", to);
  strcpy(out.type, "who");

  imc_addkey(&out.data, "type", type);

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

/* respond to a who request with the given data */
void imc_send_whoreply(const char *to, const char *data, int sequence)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  if (!strcmp(imc_mudof(to), "*"))
    return; /* don't let them do this */

  imc_initdata(&out.data);

  imc_sncpy(out.to, to, IMC_NAME_LENGTH);
  strcpy(out.type, "who-reply");
  strcpy(out.from, "*");
  imc_addkey(&out.data, "text", data);
  if (sequence!=-1)
    imc_addkeyi(&out.data, "sequence", sequence);
  
  imc_send(&out);
  imc_freedata(&out.data);
}

/* special handling of whoreply construction for sequencing */
static char *wr_to;
static char *wr_buf;
static int wr_sequence;

void imc_whoreply_start(const char *to)
{
  wr_sequence=0;
  wr_to=imc_strdup(to);
  wr_buf=imc_getsbuf(IMC_DATA_LENGTH);
}

void imc_whoreply_add(const char *text)
{
  /* give a bit of a margin for error here */
  if (strlen(wr_to) + strlen(text) >= IMC_DATA_LENGTH-500)
  {
    imc_send_whoreply(wr_to, wr_buf, wr_sequence);
    wr_sequence++;
    imc_sncpy(wr_buf, text, IMC_DATA_LENGTH);
    return;
  }

  strcat(wr_buf, text);
}

void imc_whoreply_end(void)
{
  imc_send_whoreply(wr_to, wr_buf, -(wr_sequence+1));
  imc_strfree(wr_to);
  wr_buf[0]=0;
  imc_shrinksbuf(wr_buf);
}

/* send a whois-request to a remote mud */
void imc_send_whois(const imc_char_data *from, const char *to)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  if (strchr(to, '@'))
    return;

  setdata(&out, from);

  sprintf(out.to, "%s@*", to);
  strcpy(out.type, "whois");

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

/* respond with a whois-reply */
void imc_send_whoisreply(const char *to, const char *data)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  if (!strcmp(imc_mudof(to), "*"))
    return; /* don't let them do this */

  imc_initdata(&out.data);

  imc_sncpy(out.to, to, IMC_NAME_LENGTH);
  strcpy(out.type, "whois-reply");
  strcpy(out.from, "*");
  imc_addkey(&out.data, "text", data);

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

/* beep a remote player */
void imc_send_beep(const imc_char_data *from, const char *to)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  if (!strcmp(imc_mudof(to), "*"))
    return; /* don't let them do this */

  setdata(&out, from);
  strcpy(out.type, "beep");
  imc_sncpy(out.to, to, IMC_NAME_LENGTH);

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

/* send a keepalive to everyone */
void imc_send_keepalive(void)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  imc_initdata(&out.data);
  strcpy(out.type, "is-alive");
  strcpy(out.from, "*");
  strcpy(out.to, "*@*");
  imc_addkey(&out.data, "versionid", IMC_VERSIONID);
  if (imc_siteinfo.flags[0])
    imc_addkey(&out.data, "flags", imc_siteinfo.flags);

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

/* send a ping with a given timestamp */
void imc_send_ping(const char *to, int time_s, int time_u)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  imc_initdata(&out.data);
  strcpy(out.type, "ping");
  strcpy(out.from, "*");
  strcpy(out.to, "*@");
  imc_sncpy(out.to+2, to, IMC_MNAME_LENGTH-2);
  imc_addkeyi(&out.data, "time-s", time_s);
  imc_addkeyi(&out.data, "time-us", time_u);

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

/* send a pingreply with the given timestamp */
void imc_send_pingreply(const char *to, int time_s, int time_u, const char *path)
{
  imc_packet out;

  if (imc_active<IA_UP)
    return;

  imc_initdata(&out.data);
  strcpy(out.type, "ping-reply");
  strcpy(out.from, "*");
  strcpy(out.to, "*@");
  imc_sncpy(out.to+2, to, IMC_MNAME_LENGTH-2);
  imc_addkeyi(&out.data, "time-s", time_s);
  imc_addkeyi(&out.data, "time-us", time_u);
  imc_addkey(&out.data, "path", path);

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