/*
 * IMC2 - an inter-mud communications protocol
 *
 * webserver.c: the server for a who-list-on-web-page thing
 *
 * 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.
 *
 */

/* Consider this alpha code. 'nuff said */

#include <stdlib.h> 
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <sys/un.h>
#include <ctype.h>
#include <sys/stat.h>
#include <errno.h>

#include "imc.h"

#ifndef CONFIG
#define CONFIG "cgi/"
#endif

#define NOICED
int requests;

char *url;
void icec_shutdown(void) /* dummy catch for non muds -shogar*/
{
}
void ev_iced_refresh(void *dummy) /*dummy catch cause cgi has no iced.c-shogar */
{
}

void *imc_malloc(int size)
{
  return malloc(size);
}

void IMC_free(void *block, int size)
{
  if(block)
  	free(block);
}

char *imc_strdup(const char *orig)
{
  return strdup(orig);
}

void IMC_strfree(char *str)
{
  if(str)
  	free(str);
}

int imc_readconfighook(const char *word, const char *value)
{
  if (!strcasecmp(word, "URL"))
  {
    if (url)
      imc_strfree(url);
    url=imc_strdup(value);
    return 1;
  }

  return 0;
}

void imc_saveconfighook(FILE *fp)
{
  if (url)
    fprintf(fp, "URL %s\n", url);
}

/* logging functions */

void imc_debug(const imc_connect *c, int out, const char *string)
{
#if 0
  /* this is rarely useful */

  char *dir;

  dir=out ? "<" : ">";

  fprintf(stdout, "%s %s %s\n", imc_getconnectname(c), dir, string);
#endif
}

void imc_log(const char *string)
{
  char buf[IMC_DATA_LENGTH];

  strcpy(buf, ctime(&imc_now));
  buf[strlen(buf)-1]=' ';
  fprintf(stderr, "%s%s\n", buf, string);
}

void imc_recv_who(const imc_char_data *from, const char *type)
{
  char arg[IMC_DATA_LENGTH];
  char buf[IMC_DATA_LENGTH];
  
  type=imc_getarg(type, arg, IMC_DATA_LENGTH);

  if (!strcasecmp(arg, "who") || !strcasecmp(arg, "info"))
  {
    if (!url)
      imc_send_whoreply(from->name, "This is a CGI interface site.\n\r", -1);
    else
    {
      sprintf(buf,
	      "This is a CGI interface site.\n\r"
	      "Its output can be accessed at %s.\n\r", url);
      imc_send_whoreply(from->name, buf, -1);
    }
  }
  else if (!strcasecmp(arg, "direct"))
    imc_send_whoreply(from->name, imc_list(0), -1);
  else if (!strcasecmp(arg, "list"))
    imc_send_whoreply(from->name, imc_list(3), -1);
  else if (!strcasecmp(arg, "config"))
    imc_send_whoreply(from->name, imc_list(4), -1);
  else if (!strcasecmp(arg, "istats"))
  {
    strcpy(buf, imc_getstats());
    sprintf(buf+strlen(buf),
	    "\n\rProcessed requests: %d\n\r", requests);
    imc_send_whoreply(from->name, buf, -1);
  }
  else if (!strcasecmp(arg, "help") || !strcasecmp(arg, "services") ||
	   !strcasecmp(arg, "help"))
    imc_send_whoreply(from->name,
		      "Available rquery types:\n\r"
		      "help       - this list\n\r"
		      "info       - server information\n\r"
		      "list       - active IMC connections\n\r"
		      "istats     - IMC statistics\n\r", -1);
 
  else
    imc_send_whoreply(from->name,
		      "Sorry, no information of that type is available", -1);
}

void imc_recv_whois(const imc_char_data *from, const char *to)
{
}

void imc_recv_whoisreply(const char *to, const char *text)
{
}

void imc_recv_tell(const imc_char_data *from, const char *to, const char *text,
                   int isreply)
{
}

void imc_recv_chat(const imc_char_data *from, int channel, const char *text)
{
}

void imc_recv_emote(const imc_char_data *from, int channel, const char *text)
{
}

void imc_recv_beep(const imc_char_data *from, const char *to)
{
}

void imc_traceroute(int ping, const char *pathto, const char *pathfrom)
{
}

char *imc_mail_arrived(const char *from, const char *to, const char *date,
		       const char *subject, const char *text)
{
  return "This is a webserver interface only, and does not accept mail.";
}



/* Core of the code! */

/* now handles rwho sequencing for those muds that support it */

typedef struct _replydata {
  char *text;
  int sequence;
  struct _replydata *next;
} replydata;

typedef struct {
  int fd;
  int id;
  int length;
  int received;
  replydata *reply;
} requestdata;

requestdata clients[20];

int id;
int freeclients;

void sendlist(int fd)
{
  char buf[10000];
  imc_reminfo *r;

  buf[0]=0;
  for (r=imc_reminfo_list; r; r=r->next)
  {
    sprintf(buf+strlen(buf),
            "%s %s %d\n", r->name, r->version, r->ping);
  }

  write(fd, buf, strlen(buf));
}

void complete_request(int index)
{
  replydata *r, *next;
  int expected=0;
  
  for (r=clients[index].reply; r; r=next)
  {
    next=r->next;
    
    if (r->sequence != expected)
    {
      char buf[100];
      while (expected<r->sequence)
      {
	sprintf(buf, "\n\r[missing data for sequence %d]\n\r", expected);
	write(clients[index].fd, buf, strlen(buf));
	expected++;
      }
    }
    
    write(clients[index].fd, r->text, strlen(r->text));
    expected++;

    imc_strfree(r->text);
    imc_free(r, sizeof(*r));
  }

  close (clients[index].fd);
  clients[index].fd=-1;
  
  requests++;
}

void add_request(int index, const char *text, int sequence)
{
  replydata *r, *search;

  if (clients[index].length < sequence)
  {
    imc_logerror("add_request: sequence higher than max?");
    return;
  }
  
  r=imc_malloc(sizeof(*r));
  r->sequence=sequence;
  r->text=imc_strdup(text);
  r->next=NULL;

  if (!clients[index].reply)
    clients[index].reply=r;
  else
  {
    for (search=clients[index].reply;
	 search->next && search->next->sequence < sequence;
	 search=search->next)
      ;

    if (search->next && search->next->sequence == sequence)
    {
      imc_logerror("add_request: duplicate rwho sequence?");
      imc_strfree(r->text);
      imc_free(r, sizeof(*r));
    }
    else
    {
      r->next=search->next;
      search->next=r;
    }
  }

  clients[index].received++;
  if (clients[index].length &&
      clients[index].received == clients[index].length)
    complete_request(index);
}

void free_reply(replydata *r)
{
  replydata *next;

  for (; r; r=next)
  {
    next=r->next;
    imc_strfree(r->text);
    imc_free(r, sizeof(*r));
  }
}

void imc_recv_whoreply(const char *to, const char *text, int sequence)
{
  int i, j;

  i=atoi(to);
  if (!i)
    return;

  for (j=0; j<20; j++)
    if (clients[j].fd!=-1 && clients[j].id==i)
    {
      if (sequence<0)
      {
	clients[j].length=-sequence;
	add_request(j, text, -sequence-1);
      }
      else
	add_request(j, text, sequence);
      return;
    }
}

void runclient(int clientfd)
{
  char buf[1000];
  char arg[100];
  char name[IMC_NAME_LENGTH];
  const char *p;
  int r;
  imc_char_data ch;
  int i;

  /* set up */

  for (i=0; i<20; i++)
    if (clients[i].fd==-1)
      break;

  if (i==20)
  {
    imc_log("runclient: no free clients?!");
    close(clientfd);
    return;
  }

  /* read the request */
  alarm(5);

  /* we're fucked if we don't have EINTR, but too bad */
  r=read(clientfd, buf, 1000);
  if (r<0)
  {
    alarm(0);
#ifndef NO_EINTR
    if (errno!=EINTR)
#endif
      imc_logerror("read");
    return;
  }
  
  alarm(0);

  buf[r--]=0;
  while (r>=0 && !isalnum(buf[r]))
    buf[r--]=0;

  p=imc_getarg(buf, arg, 100);
  imc_getarg(p, name, IMC_NAME_LENGTH);
  
  if (!strcasecmp(arg, "who") || !strcasecmp(arg, "info"))
  {
    clients[i].fd=clientfd;
    clients[i].id=id;
    clients[i].length=0;
    clients[i].received=0;
    clients[i].reply=NULL;
    
    freeclients--;
  
    ch.wizi=ch.invis=ch.level=0;
    sprintf(ch.name, "%d", id);
    
    id++;
    
    imc_send_who(&ch, name, buf);
  }
  else if (!strcasecmp(arg, "list"))
  {
    sendlist(clientfd);
    close(clientfd);
  }
  else
  {
    close(clientfd);
  }
}

int main(int argc, char *argv[])
{
  int fd;
  struct sockaddr_un sa;
  int r;
  int maxfd;

  if (argc<2)
  {
    fprintf(stderr, "No socket path specified!\n");
    exit(1);
  }

  signal(SIGPIPE, SIG_IGN);

  imc_startup(CONFIG);
  if ((imc_active < IA_UP) || (imc_lock_file < 0))
  {
    imc_logstring("giving up..");
    /* imc failed to start up, or there's another process on this config */
    imc_shutdown();
    exit(0);
  }

  /* *whap self* why the hell did I have these reversed before? */
  fd=socket(AF_UNIX, SOCK_STREAM, 0);
  if (fd<0)
  {
    imc_lerror("CGI socket creation");
    exit(1);
  }

  sa.sun_family=AF_UNIX;
  strcpy(sa.sun_path, argv[1]);

  unlink(argv[1]); /* toast any old sockets */
  if (bind(fd, (struct sockaddr *)&sa, sizeof(sa))<0)
  {
    imc_lerror("CGI socket bind");
    exit(1);
  }

  if (listen(fd, 5)<0)
  {
    imc_lerror("CGI socket listen");
    exit(1);
  }

  for (r=0; r<20; r++)
    clients[r].fd=-1;

  id=1;
  freeclients=20;

  /* make the socket world-read/write/exec-able */
  chmod(argv[1], 0777);

  while(1)
  {
    int size;
    fd_set in_set, out_set, exc_set;
    struct timeval tv;
    int i;

    /* listen for requests */
    FD_ZERO(&in_set);
    FD_ZERO(&out_set);
    FD_ZERO(&exc_set);

    maxfd=-1;
    
    if (freeclients)
    {
      FD_SET(fd, &in_set);
      maxfd=fd;
    }
    
    for (r=0; r<20; r++)
      if (clients[r].fd!=-1)
      {
	FD_SET(clients[r].fd, &in_set);
	if (clients[r].fd > maxfd)
	  maxfd=clients[r].fd;
      }
    
    tv.tv_sec=imc_get_max_timeout();
    tv.tv_usec=0;
      
    maxfd=imc_fill_fdsets(maxfd, &in_set, &out_set, &exc_set);
    
#if NO_EINTR
    i=select(maxfd+1, &in_set, &out_set, &exc_set, &tv);
#else
    while ((i=select(maxfd+1, &in_set, &out_set, &exc_set, &tv))<0 &&
           errno==EINTR)
      ;
#endif

    if (i<0)
    {
      imc_lerror("select");
      exit(0);
    }

    imc_idle_select(&in_set, &out_set, &exc_set, time(NULL));
      
    if (FD_ISSET(fd, &in_set))
    {
      size=sizeof(sa);
      r=accept(fd, (struct sockaddr *)&sa, &size);
      if (r<0)
	imc_lerror("CGI socket accept"); 
      else
	runclient(r);
    }
    
    for (r=0; r<20; r++)
      if (clients[r].fd!=-1 && FD_ISSET(clients[r].fd, &in_set))
      {
	char dummy[100];
	if (read(clients[r].fd, dummy, 100)<=0)
	{
	  close(clients[r].fd);
	  clients[r].fd=-1;
	  free_reply(clients[r].reply);
	  freeclients++;
	}
      }
  }

  /* never reached */
}

