#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#ifdef CONVERSE
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "converse.h"
#else !CONVERSE
#include <hosttab.h>
#include <sys/chaos.h>
#endif
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
#include <utmp.h>
#ifdef BSD42
#include <sys/time.h>
#else
#ifndef ZEPHYR
#include <time.h>		/* zephyr includes this */
#endif
#endif
#include <pwd.h>

#ifdef ZEPHYR
#include <zephyr/mit-copyright.h>
#include <zephyr/zephyr_internal.h>
#include <zephyr/zephyr.h>
#endif ZEPHYR

/*
#ifdef CONVERSE
 * CONVERSE server from another user on the Internet
#else !CONVERSE
 * SEND server from another user on the chaosnet
#endif !CONVERSE
 *
 * Compile with CONVERSE defined to get a TCP Lisp machine Converse server,
 * invokable from inetd.  Add DEBUG, and it will listen for & handle a single
 * connection.  Compile with CONVERSE_PORT defined to be a number to override
 * /etc/services' idea of the converse/tcp port number.  Compile with STANDALONE
 * defined and the server will run on its own, without inetd.  STANDALONE turns
 * on DEBUG, obviously.
 *
 * Compile with CONVERSE undefined to get the original CHAOS/SEND server.
 *
 * Compile with ZEPHYR defined to try delivering via zephyr
 *
 * Compile with SPOOL defined to be the
 * pathname of a directory in which messages are stored on a per-user basis (eg
 * "/usr/spool/sends", which might have files named "fred", "root", and "virus").
 * By default, SPOOL is "/usr/spool/sends".
 */


/* $Log:	SEND.c,v $
 * Revision 1.1  89/09/24  12:49:15  gdt
 * Initial revision
 * 
 * Revision 1.2  86/10/12  13:12:21  mbm
 * Use group write permission on tty for 4.3
 *  */

#ifdef STANDALONE
#ifndef DEBUG
#define DEBUG
#endif !DEBUG
#endif STANDALONE

/* CHAOS/SEND + TCP/CONVERSE intercompatibility */
#ifdef CONVERSE
#define REJECT(fd, why) write(fd, "-", 1), write(fd, why, sizeof(why)-1), write(fd, "\215", 1)
#else !CONVERSE
#define REJECT(fd, why) chreject(fd, why)
#endif !CONVERSE

#define MAXLINES 64
#define MAXLINELEN 256

char *lines[MAXLINES] = {0};
char display[64] = {0};

#define SPOOL "/usr/spool/sends"

struct	utmp ubuf;
#define NMAX sizeof(ubuf.ut_name)
#define LMAX sizeof(ubuf.ut_line)
#define FALSE 0
#define TRUE 1
#define bool int

char of[sizeof(SPOOL)+1+NMAX+1];

char	*strcat();
char	*strcpy();
int	signum[] = {SIGHUP, SIGINT, SIGQUIT, 0};
char	me[10]	= "???";
char	*him = NULL;
char	*mytty;
char	histty[32];
char	*histtya;
char	*ttyname();
char	*rindex();
int	logcnt;
int	eof();
int	timout();

char 	*malloc(), *realloc(), free();;
char	*getenv();

main(argc, argv)
char *argv[];
{
  struct stat stbuf;
  register i;
  register FILE *uf;
  int c1, c2;
  bool brief = FALSE;	/* for CHAOS/NOTIFY like performance */
#ifdef ZEPHYR
  bool use_zephyr = FALSE;
#endif
  long	clock = time( 0 );
  struct tm *localtime();
  struct tm *localclock = localtime( &clock );
  int ofd;
  struct passwd *pwd, *getpwnam();

#ifdef CONVERSE
  char buf[BUFSIZ], *cp, *host_name;
  char also_to[100], date[100], from[100], to[100];
  struct hostent *hp;
  struct sockaddr_in sin;
#ifdef DEBUG
  struct servent *servp;
  int s, ns, foo;
#endif
#else !CONVERSE
  struct chstatus chst;
#endif !CONVERSE

  /* stuff for buffer */
  int linectr = 0, charctr = 0;
  char *line;

#ifdef CONVERSE
  /* If we're not debugging, we'll be started up by inetd
     with the connected socket as file descriptor 0 */
#ifdef DEBUG
  /* Look up service */
  if ((servp = getservbyname("converse", "tcp")) == NULL) {
    fprintf(stderr, "converse/tcp: No such service\n");
#ifndef CONVERSE_PORT
    exit(1);
#endif
  }

  /* Open socket */
  if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    exit(1);
  }

  /* Bind to converse port */
  sin.sin_family = AF_INET;
#ifdef CONVERSE_PORT
  sin.sin_port = htons(CONVERSE_PORT);
#else !CONVERSE_PORT
  sin.sin_port = servp->s_port;
#endif !CONVERSE_PORT
  sin.sin_addr.s_addr = INADDR_ANY;
  if (bind(s, (char *) &sin, sizeof(struct sockaddr_in)) == -1) {
    perror("bind");
    exit(1);
  }

  /* Listen to socket */
  if (listen(s, 5) == -1) {
    perror("listen");
    exit(1);
  }

#ifdef STANDALONE
 listen:
#endif STANDALONE

  /* Accept incoming connection */
  foo = sizeof(struct sockaddr_in);
  if ((ns = accept(s, &sin, &foo)) < 0) {
    perror("accept");
    exit(1);
  }

#ifdef STANDALONE
  if ((i = fork()) == -1) {
    /* Error */
    perror("fork");
    exit(1);
  } else if (i > 0) {
    /* Parent */
    goto listen;		/* So sue me */
  }
  /* Child */
#endif STANDALONE

  /* Set things up as if we were started by inetd
   * Note that if we're running in DEBUG mode, we only handle one connection. */
  dup2(ns, 0);
  close(s);
#endif DEBUG		  
  /* A converse transaction consists of envelope information, then a blank line,
   * then the text of the message.  After receiving the blank line, the server
   * must respond with a single line, the first character of which signifies
   * acceptance of the message for delivery ('+'), acceptance of the message
   * for delivery to some other recipient ('%', not used by this implementation),
   * or rejection of the message ('-').  After actually delivering the message
   * th server sends a similar confirmation message. All lines are ended by CHNL, \215.
   * The text is terminated by EOF.  Each envelope line consists of a keyword
   * followed by data followed by the end of the line. Case is insignificant.
   * Currently defined keywords are date, to, from, also-to, ident, private,
   * encrypted, notification, character-type-mappings, and fonts.  This server
   * accepts all of them except encrypted, and discards everything except date,
   * to, from, also-to, and notification.  Date, from, and also-to are used only
   * in the header of the delivered message.  Anybody who wants to do something
   * clever with character-type-mappings and X should feel free.
   */

  /* Set first chars to '\0' as a flag that the buffer is empty */
  also_to[0] = date [0] = from[0] = to[0] = '\0';

  for (;;) {
    if (get_line(buf, BUFSIZ) == -1) {
      perror("-get_line");
      exit(1);
    }

    /* Empty line means end of envelope information */
    if (buf[0] == '\0')
      break;
    
    /* Parse line */
    if ((cp = index(buf, ' ')) == NULL) {
      REJECT(0, "No keyword on line");
      exit(1);
    }
    *cp++ = '\0';
    for (i = 0; i < N_CONVERSE_KEYWORDS; i++)
      if (!strcasecmp(buf, converse_kwds[i]))
	break;

    switch (i) {
    case CONVERSE_ALSO_TO:
      strcpy(also_to, cp);
      break;

    case CONVERSE_DATE:
      strcpy(date, cp);
      break;

    case CONVERSE_FROM:
      strcpy(from, cp);
      break;

    case CONVERSE_NOTIFICATION:
      if (!strcasecmp(cp, "yes"))
	brief = TRUE;
      break;

    case CONVERSE_TO:
      strcpy(to, cp);
      /* If we were handed user@host, we only want user for utmp'ing.
       * An argument could be made that we should check the hostname
       * and make sure it's one of ours.  Feel free to implement this. */
      if ((cp = index(to, '@')) != NULL)
	*cp = '\0';
      him = to;
      break;

    case CONVERSE_CHARACTER_TYPE_MAPPINGS:
    case CONVERSE_FONT:
    case CONVERSE_IDENT:
    case CONVERSE_PRIVATE:
      break;

    default:
      REJECT(0, "Unknown header");
      exit(1);
    }
  }

  /* Require to, default from and date if necessary */
  if (him == NULL) {
    REJECT(0, "No recipient specified");
    exit(1);
  }

  /* Look up name of remote host */
  c1 = sizeof(struct sockaddr_in);
  if (getpeername(0, (char *) &sin, &c1) == -1) {
    perror("getpeername");
    exit(1);
  }
  if ((hp = gethostbyaddr((char *) &(sin.sin_addr), sizeof(struct in_addr), AF_INET)) == NULL)
    host_name = inet_ntoa(sin.sin_addr);
  else
    host_name = hp->h_name;

  if (from[0] == '\0')
    strcpy(from, "Unknown");

  /* Canonicalize host name */
  if ((cp = index(from, '@')) == NULL)
    sprintf(from + strlen(from), "@%s", host_name);
  else if (strcasecmp(cp+1, host_name))
    sprintf(from + strlen(from), " (%s)", host_name);

  /* At this point we are going to accept the message,
     so let the other end know */
#define ACCEPT "+Sock it to me\215"
  write(0, ACCEPT, sizeof(ACCEPT) - 1);

  /* Set up lines with headers */
  sprintf(buf, "[%s%s %s%s",
	  brief == TRUE ? "" : "Message from ",
	  from, date,
	  (brief == FALSE) && (also_to[0] != '\0') ? "" : "]");
  lines[linectr] = malloc((strlen(buf) + 1) * sizeof(char));
  strcpy(lines[linectr++], buf);

  /* Elide any Also-to header if we're in notification mode */
  if ((brief == FALSE)  && (also_to[0] != '\0')) {
    sprintf(buf, " Also-to: %s]", also_to);
    lines[linectr] = malloc((strlen(buf) + 1) * sizeof(char));
    strcpy(lines[linectr++], buf);
  }
#else !CONVERSE
  if(argc < 2)
    {
      REJECT(0, "No user name supplied");
      exit(1);
    }
  
  /* get target username */
  him = argv[1];
#endif !CONVERSE

  lcase(him);			/* lowercase users only! */
  /* target starts with '-' implies brief mode */
  if( him[0] == '-' )
    {
      him++;
      brief = TRUE;
    }

#ifndef CONVERSE
  if (argc > 2)
    histtya = argv[2];
#endif !CONVERSE

#ifdef ZEPHYR
  if( zping(him, brief) == 0 )
    use_zephyr = 1;
  else
    {
#endif

  if ((uf = fopen("/etc/utmp", "r")) == NULL)
    {
#ifndef CONVERSE		/* This is not quite right */
      printf("cannot open /etc/utmp\n");
#endif !CONVERSE
      goto cont;
    }

  if (histtya)
    {
      strcpy(histty, "/dev/");
      strcat(histty, histtya);
    }
  
  /* magic code to parse utmp
     I don't understand the '-' stuff
     */
  while (fread((char *)&ubuf, sizeof(ubuf), 1, uf) == 1)
    {
      if (ubuf.ut_name[0] == '\0')
	continue;
      if(him[0] != '-' || him[1] != 0)
	for(i=0; i<NMAX; i++)
	  {
	    c1 = him[i];
	    c2 = ubuf.ut_name[i];
	    if(c1 == 0)
	      if(c2 == 0 || c2 == ' ')
		break;
	    if(c1 != c2)
	      goto nomat;
	  }
      logcnt++;
      if (histty[0]==0)
	{
	  strcpy(histty, "/dev/");
	  strcat(histty, ubuf.ut_line);
	}
    nomat:
      ;
    }

 cont:
  if (logcnt==0 && histty[0]=='\0')
    {
      REJECT(0, "User not logged in.");
      exit(1);
    }
  fclose(uf);
  if(histty[0] == 0)
    {
      REJECT(0, "User not logged in out given tty.");
      exit(1);
    }
  if (access(histty, 0) < 0)
    {
      REJECT(0, "User's tty non-existent.");
      exit(1);
    }

  signal(SIGALRM, timout);
  alarm(5);

  if (freopen(histty, "w", stdout) == NULL)
    goto perm;

  alarm(0);			/* no more worry about hanging */

  if (fstat(fileno(stdout), &stbuf) < 0)
    goto perm;
  if ((stbuf.st_mode&020) == 0)	/* group write permission to allow sends */
    goto perm;

#ifdef ZEPHYR
}
#endif ZEPHYR

#ifndef CONVERSE
  /* accept the connection */
  ioctl(0, CHIOCACCEPT, 0);
  ioctl(0, CHIOCGSTAT, &chst);
#endif !CONVERSE

  /* read the data into a buffer */
  line = NULL;

  /* loop on each character */
  while( (c1 = getchar()) != EOF )
    {
      switch(c1)
	{
	  /* finish line if currently on line, else punt */
	  /* multiple NL's are punted - this seems wrong */
	case CHNL:
	case '\n':
	case '\r':
	  if( line != NULL )
	    {
	      line[charctr] = '\0';
	      line = NULL;
	    }
	  break;
	 
	case CHTAB:
	  c1 = '\t';
	  /* fall through! */

	  /* reg char ==> start a new line if need be, and add it */
	default:
	  if( line == NULL )
	    {
	      if( linectr >= MAXLINES )
		linectr--;
	      line = lines[linectr++] = malloc(MAXLINELEN*sizeof(char));
	      if( line == NULL )
		die("Malloc failed");
	      charctr = 0;
	    }

	  if( charctr < MAXLINELEN )
	    line[charctr++] = c1;
	  break;
	}			/* switch */
    }				/* while */

  /* finish off last line if need be */
  if( line != NULL )
    {
      line[charctr] = '\0';
      line = NULL;
    }

#ifndef CONVERSE
  /* fix first line */
  line = malloc((MAXLINELEN+64)*sizeof(char));
  if( line == NULL )
    die("malloc failed");

  sprintf(line, "[%s%s]", brief ? "" : "Message from ", lines[0]);

  free(lines[0]);
  lines[0] = line;
  line = NULL;

  /* free connection */
  {
    char randombuf[1024];
    read(0, randombuf, 1024);

    close(0);
  }
#endif

#ifdef SPOOL
  /* output to spool directory file */
  if (brief == FALSE &&
      ((ofd = open(sprintf(of, "%s/%s", SPOOL, him), 1)) >= 0 || 
       (ofd = creat(of, 0622)) >= 0))
    {
      if ((pwd = getpwnam(him)) == NULL ||
	  chown(of, pwd->pw_uid, pwd->pw_gid) < 0)
	chmod(of, 0666);
      lseek(ofd, 0L, 2);
      for(linectr = 0; lines[linectr] != NULL; linectr++)
	{
	  write(ofd, lines[linectr], strlen(lines[linectr]));
	  write(ofd, "\n", 1);
	}
      write(ofd,"\004",1);
    }
#endif SPOOL

#ifdef ZEPHYR
  if ( ! use_zephyr || zdeliversend(him, brief) != 0)
    {
#endif

  printf("\7\n\r");
  for(linectr = 0; lines[linectr] != NULL; linectr++)
    {
      write(fileno(stdout), lines[linectr], strlen(lines[linectr]));
      write(fileno(stdout), "\n\r", 2);
    }
#ifdef ZEPHYR
}
#endif
  confirm_delivery();
  
  exit(0);
 perm:
  REJECT(0, "Permission denied on his tty.");
  exit(1);
}

timout()
{

	REJECT(0, "Timeout opening their tty.");
	exit(1);
}

confirm_delivery ()
{
#ifdef CONVERSE
  /* Confirm delivery */
#define CONFIRM "+We be jammin'\215"
  write(0, CONFIRM, sizeof(CONFIRM)-1);
#endif CONVERSE
}

lcase(s)
register char *s;
{
	while (*s)
		if (isupper(*s))
			*s++ = tolower(*s);
		else
			s++;
}

die(s)
char *s;
{
  exit(-1);
}

#ifdef CONVERSE
get_line (buf, bufsiz)
     char *buf;
     int bufsiz;
{
  int c;
  char *endp = &buf[bufsiz-1];

  while (buf != endp && (c = getchar()) >= 0)
    if (c == CHNL) {
      *buf++ = '\0';
      break;
    } else if (c == CHTAB)
      *buf++ = '\t';
    else
      *buf++ = (char) c;

  if (c < 0)
    return(-1);
  else
    return(0);
}
#endif CONVERSE

#ifdef ZEPHYR
/* return 0 if user is logged in */
zping(user, brief)
char *user;
{
  ZNotice_t notice, retnotice;
  int retval;

  if ((retval = ZInitialize()) != ZERR_NONE) {
    return(-1);
  } 

  /* others seem to do this... */
  bzero((char *) &notice, sizeof(notice));

  notice.z_kind = ACKED;
  notice.z_port = 0;
  notice.z_class = "message";
  notice.z_class_inst = brief ? "brief" : "personal";
  notice.z_opcode = "PING";
#ifdef CONVERSE
  notice.z_sender = "tcp/converse";
#else
  notice.z_sender = "CHAOS/SEND";
#endif
  notice.z_message_len = 0;
  notice.z_recipient = user;
  retval = ZSendNotice(&notice, ZNOAUTH);

  if (retval != ZERR_NONE)
    return(-1);

  if ((retval = ZIfNotice(&retnotice, (struct sockaddr_in *) 0,
			  ZCompareUIDPred, 
			  (char *)&(notice.z_uid))) !=
      ZERR_NONE)
    goto zlose;

  if (retnotice.z_kind == SERVNAK)
    goto zlose;
  if (retnotice.z_kind != SERVACK || !retnotice.z_message_len)
    goto zlose;

  if (!strcmp(retnotice.z_message, ZSRVACK_NOTSENT)) {
    /* user not getting messages */
    ZFreeNotice(&retnotice);
    return(1);
  }

  /* passed all checks! - notification was acked */
  return(0);

 zlose:
    ZFreeNotice(&retnotice);
    return(-1);
}


/* returns 0 if successful, 1 if not, and -1 if zephyr error */
zdeliversend(user, brief)
char *user;			/* user to deliver to */
bool brief;
{
  ZNotice_t notice, retnotice;
  char *fields[2];
  int retval;
  char *msg;
  char *malloc();
  int msglen;
  int linectr;

  for( msglen = 0, linectr = 0; lines[linectr] != NULL; linectr++ )
    msglen += strlen(lines[linectr]) + 1;

  if ( (msg = malloc(msglen+100)) == NULL )
    return(-1);

  *msg = '\0';

  for( linectr = 0; lines[linectr] != NULL; linectr++ )
    {
      strcat(msg, lines[linectr]);
      strcat(msg, "\n");
    }

#ifdef GDT-DEBUG
  {
    int fdgdt = creat("/tmp/conv-dump", 0644);
    write(fdgdt, msg, msglen);
  }
#endif

  if ((retval = ZInitialize()) != ZERR_NONE) {
    return(-1);
  } 

  /* others seem to do this... */
  bzero((char *) &notice, sizeof(notice));

  notice.z_kind = ACKED;
  notice.z_port = 0;
  notice.z_class = "MESSAGE";
  notice.z_class_inst = brief ? "BRIEF" : "PERSONAL";
#ifdef CONVERSE
  notice.z_sender = "tcp/converse";
#else
  notice.z_sender = "CHAOS/SEND";
#endif
  notice.z_recipient = user;
  notice.z_message = msg;
  notice.z_message_len = msglen;
/*  notice.z_default_format = "$1";
  fields[0] = "";
  fields[1] = msg;
  retval = ZSendList(&notice, fields, 2, ZNOAUTH);
*/
  retval = ZSendNotice(&notice, ZNOAUTH);

  if (retval != ZERR_NONE)
    return(-1);

  if ((retval = ZIfNotice(&retnotice, (struct sockaddr_in *) 0,
			  ZCompareUIDPred, 
			  (char *)&(notice.z_uid))) !=
      ZERR_NONE)
    goto zlose;

  if (retnotice.z_kind == SERVNAK)
    goto zlose;
  if (retnotice.z_kind != SERVACK || !retnotice.z_message_len)
    goto zlose;

  if (!strcmp(retnotice.z_message, ZSRVACK_NOTSENT)) {
    /* user not getting messages */
    ZFreeNotice(&retnotice);
    return(1);
  }

  /* passed all checks! - notification was acked */
  return(0);

 zlose:
    ZFreeNotice(&retnotice);
    return(-1);
}
#endif
