/*
 * File:	sendmsg.c
 *
 * Author:	Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 *
 * History:	
 * 
 *   1995-08-11	Framstag	initial version
 *   1995-08-12	Framstag	elm alias support
 *   1995-11-02	Framstag	added minimal chat mode
 *   1995-11-14	Framstag	added message receiving modes
 *   1995-12-21	Framstag	avoid unnecessary error message while 
 *                              configuring the own tty
 *   1995-12-21	Framstag	better server connect testing
 *   1996-02-20	Framstag	changed msg-tty file to support NFS
 *   1996-04-01	Framstag	added multiline mode
 *   1996-04-17	Framstag	new error handling for open_connection
 *   1996-05-02	Framstag	fixed stupid shutdown() programming bug
 *   1996-05-03	Framstag	fixed bug with gethostname()
 *   1996-05-24	Framstag	sendmsg no longer tries to configure the tty 
 *                              when there is none (sending via pipe)
 *   1996-08-12	Framstag	no questions asked when in batch mode (no tty)
 *   1996-11-13	Framstag	faster mesg/tty handling
 *   1996-11-26	Framstag	added -n option
 *   1997-01-04	Framstag	added saft_connect()-call
 *   1997-02-03	Framstag	better tty configuration
 * 				sprintf() -> snprintf()
 * 				added -u and -f options
 * 				added -u and -f options
 * 				changed tty permission testing and -m behaviour
 *   1997-02-23	Framstag	modified str_* function names
 *   1997-06-19	Framstag	changend tty testing and behaviour
 *   1997-06-23	Framstag	added readline support
 *   1997-06-24	Framstag	fixed some prompt bugs
 *   1997-07-04	Framstag	added cleanup signal handler
 *   1998-01-03	Framstag	better tty configuration
 *   1998-01-17	Framstag	check SAFT-URL for alternative tcp port
 *   1998-03-29	Framstag	"sendmsg -m 2>/dev/null" now works
 *   1998-04-06	Framstag	added -s message option
 *   1998-07-12	Framstag	added option -r 
 *   1998-08-21	Framstag	fixed bug if tty can receive msg
 *   1998-09-17	Framstag	added option -q
 *   2005-06-06 Maide           added multiprotocol cababilities
 *   2005-06-06 Maide           replaced numeric ports with service string
 *   2005-06-06 Maide           added -4/-6 command line options and
 *                              corresponding help text
 *   2006-11-27 Framstag	correct exit code now on error
 *   2008-03-11 Framstag	reconnect and resend after server timeout
 *   2008-06-16 Joey		prevent buffer overflow for getopt-strings
 *
 * The sendmessage client of the sendfile package.
 * Sends a single line text message to the SAFT-server of the destination
 * system to be displayed on the recipients terminal.
 *
 * Copyright © 1995-2008 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */

#include "config.h"		/* various #defines */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include "string.h"		/* extended string functions */
#include "net.h"		/* the network routines */
#include "io.h"			/* (socket) read/write */
#include "message.h"		/* information, warning and error messages */
#include "utf7.h"		/* UTF-7 coding */
#include "address.h"		/* address checking */
#include "getline.h"		/* get a line of text from stdin */

#if defined(HAVE_GETOPT_H)
  #include <getopt.h>
#else
  int getopt(int, char * const *, const char *);
  extern int opterr;
  extern int optind;
  extern int optopt;
  extern char *optarg;
#endif

#if defined(SOLARIS2)
  int gethostname(char *, int);
#endif

#if defined(LINUX)
  int gethostname(char *, size_t);
#endif

#ifndef AIX3
  #ifndef CONVEXOS
    FILE *popen(const char *, const char *);
  #endif
  int pclose(FILE *);
#else
  #include "bsd.h"
#endif

#ifdef NEXT
  int shutdown(int, int);
#endif


/* print short help usage text */
int usage();

/* clean termination routine */
void cleanup();


/* global variables */
int 
  xonf=1,	/* exit on fatalerror flag */
  client=1,	/* flag to determine client or server */
  quiet=0,	/* quiet mode */
  verbose=0,	/* flag for verbose mode */
  packet_size;	/* only needed for linking */
char 
  *prg;		/* name of the game */


int main(int argc, char *argv[]) {
  int
    sockfd,		/* socket file descriptor */
    chat,		/* flag for chat mode */
    reply,		/* flag for reply mode */
    force,		/* flag for force sending */
    receive,		/* receiving flag */
    multiline,		/* flag for sending a multiline message */
    opt;		/* option to test for */
  char
    *cp,		/* simple character pointer */
    *tty,		/* the tty device with path */
    *sr,		/* server reply */
    ttyt[FLEN],		/* the tty on which sendfiled will write (see msgcf) */
    recipient[FLEN], 	/* recipient at serverhost */
    user[FLEN], 	/* local user name */
    from[FLEN], 	/* alternative from name */
    host[FLEN], 	/* name of serverhost */
    localhost[FLEN], 	/* name of the local host */
    msgcf[FLEN], 	/* message control file */
    msg[MAXLEN],	/* message from ARGV */
    line[MAXLEN],	/* input or output string */
    login[MAXLEN],	/* login user name */
    tmp[3*MAXLEN];	/* temporary string */
  char
    utf_msg[LEN_UTF],	/* msg in UTF-7 format */
    iso_msg[LEN_ISO];	/* msg in ISO Latin-1 format */
  FILE 
    *inf,		/* input file */
    *outf;		/* output file */
  struct stat finfo;	/* information about a file */

  chat=0;
  force=0;
  reply=0;
  sockfd=0;
  verbose=0;
  receive=0;
  multiline=0;
  *msg=0;
  *host=0;
  *user=0;
  *from=0;
  *line=0;
  *ttyt=0;
  *iso_msg=0;
  *recipient=0;
  tty=NULL;
  
  prg=argv[0];
  if ((cp=strrchr(prg,'/'))) prg=cp+1;

  memset(msg,0,sizeof(msg));
  memset(from,0,sizeof(from));
  memset(login,0,sizeof(login));

  /* scan the command line on options */
#ifndef ENABLE_MULTIPROTOCOL
  while ((opt=getopt(argc,argv,"rcvVmMlhq?fs:u:")) > 0) {
#else
  while ((opt=getopt(argc,argv,"rcvVmMlhq?fs:u:46")) > 0) {
#endif
    switch (opt) {
      case ':':
      case 'h':
      case '?': return(usage());
      case 'c': chat=1; break;
      case 'r': reply=1; break;
      case 'q': quiet=2; break;
      case 'v': verbose=1; break;
      case 'm': receive=1; break;
      case 'M': receive=2; break;
      case 'l': multiline=1; break;
      case 'f': force=1; break;
      case 's': strncpy(msg,optarg,sizeof(msg)-1); break;
      case 'u': strncpy(from,optarg,sizeof(from)-1); break;
      case 'V': message(prg,'I',"version "VERSION" revision "REVISION); return(0);
#ifdef ENABLE_MULTIPROTOCOL
      case '4': addressFamily = PF_INET; break;
      case '6': addressFamily = PF_INET6; break;
#endif
    }
  }

  /* too few arguments? */
  if (argc-optind<1 && receive==0 && reply==0) return(usage());
  
  /* incompatible options? */
  if (*msg && (multiline || chat)) {
    multiline=chat=0;
    message(prg,'W',"you cannot specify option -s together with -c or -l");
  }

  /* get the local host name */
  if (gethostname(localhost,FLEN-1)<0) strcpy(localhost,"localhost");

  /* get own user name, recipient name and host */
  if (reply) argc=0;
  destination(argc,argv,user,recipient,host,NULL);
  if (reply && !*recipient) {
    errno=0;
    message(prg,'F',"no reply address found");
  }
  if (*from) iso2utf(user,from);
  strncpy(login,user,sizeof(login)-1);
  if ((cp=strchr(login,' '))) *cp=0;

  /* enable simple interrupt handler */
  signal(SIGTERM,cleanup);
  signal(SIGABRT,cleanup);
  signal(SIGQUIT,cleanup);
  signal(SIGHUP,cleanup);
  signal(SIGINT,cleanup);

  if (!force) {
    
    /* test the local sendfiled */
    if (verbose) printf("testing local SAFT server:\n");
#ifndef ENABLE_MULTIPROTOCOL
    sockfd=open_connection("127.0.0.1",SAFT);
#else
    sockfd=open_connection("127.0.0.1",SERVICE);
#endif
    sock_getline(sockfd,line);

    /* no local server? */
    if (!str_beq(line,"220 ") || !strstr(line,"SAFT"))
      message(prg,'W',"there is no local SAFT server - "
	      "you cannot receive messages");
    else {
   
      /* test if you can receive messages */
      snprintf(MAXS(line),"FROM %s",login);
      sock_putline(sockfd,line);
      sock_getline(sockfd,line);
      snprintf(MAXS(line),"TO %s",login);
      sock_putline(sockfd,line);
      sock_getline(sockfd,line);
      if (str_beq(line,"521 ")) {
	errno=0;
	message(prg,'F',"You are not allowed to use the sendmsg service");
      }
      if (!str_beq(line,"200 "))
	message(prg,'W',"local server error - you cannot receive messages");
    
      if (isatty(fileno(stdin))) {
       
	/* get tty name */
	if (!(tty=ttyname(fileno(stdin)))  || *tty!='/')
	  message(prg,'F',"cannot determine your tty name");
        else {

	  /* the message tty config file */
	  snprintf(MAXS(msgcf),"%s/%s/config/tty@%s",SPOOL,login,localhost);

	  /* open tty write permissions if necessary */
	  if (receive) {
	    
	    if (receive==1) {
		
	      /* mark current tty as active for receiving */
	      if ((outf=rfopen(msgcf,"w"))) {
		fprintf(outf,"%s\n",tty);
		fclose(outf);
		if (chmod(tty,S_IRUSR|S_IWUSR|S_IWGRP)<0) {
		  snprintf(MAXS(tmp),"cannot open your tty %s for writing",tty);
		  message(prg,'W',tmp);
		} else if (argc-optind<1) {
		  message(prg,'I',
			  "receiving messages is now restricted to this tty");
		}
	      } else {
		snprintf(MAXS(tmp),"cannot configure your tty "
			 "(no write access to %s)",msgcf);
		message(prg,'W',tmp);
	      }
	    }
	      
	    if (receive==2) {
	      unlink(msgcf);
	      if (argc-optind<1)
		message(prg,'I',
			"receiving messages is now possible on all ttys");
	    }
	    
	  } else { /* keep tty status and mode */

	    /* is the current tty writable? */
	    if (stat(tty,&finfo)<0 || !(finfo.st_mode&S_IWGRP)) {
	      errno=0;
	      snprintf(MAXS(tmp),"your tty %s is write protected; "
		       "try sendmsg -m",tty);
	      message(prg,'F',tmp);
	    }
	    
	    /* read the receiving tty from the message config file */
	    if ((inf=rfopen(msgcf,"r"))) {
	      fgetl(ttyt,inf);
	      fclose(inf);
	      if ((cp=strchr(ttyt,'\n'))) *cp=0;
	    } else {
	      strcpy(ttyt,tty);
	      
	      /* mark current tty as active for receiving */
	      if ((outf=rfopen(msgcf,"w"))) {
		fprintf(outf,tty);
		fclose(outf);
	      }
	      
	    }

	    /* is the current tty active for sendfiled? */
	    if (!str_eq(tty,ttyt)) {
	      errno=0;
	      message(prg,'F',
		      "you cannot receive an answer message on this tty; "
		      "try sendmsg -m or sendmsg -f");
	    }
	    
	  }
	}
      }
    }
    
    shutdown(sockfd,2);
  }
	      

  if (argc-optind<1 && !reply) return(0);
  
  /* name the local host */
  if (str_eq(host,"127.0.0.1")) strcpy(host,localhost);

  /* look for correct SAFT server and open connection */
  sockfd=saft_connect("msg",recipient,user,host,tmp);
    
  /* send several lines at once? */
  if (multiline) {
    if (isatty(fileno(stdin))) 
      printf("Enter multiline message (max 10 lines! End with Ctrl-D):\n");

    /* read until EOF */
    while (*line=0,getpromptline(line,LEN_ISO-1)) {
      
      if ((cp=strchr(line,'\n'))) *cp=0;

      /* message text too long? */
      if (strlen(iso_msg)+strlen(line)>LEN_ISO*.8) {
        errno=0;
	message(prg,'F',"message line too long");
      }

      /* add a new line if necessary */
      if (*iso_msg || !*line) strcat(iso_msg,"\r\n");
      
      /* add the text line */
      strncat(iso_msg,line,LEN_ISO-1);
      iso_msg[LEN_ISO-1]=0;
      
    }
    
    if (*iso_msg) {

      /* encode to UTF-7 */
      iso2utf(utf_msg,iso_msg);

      /* message text too long? */
      if (strlen(utf_msg)>MAXLEN-10) {
	errno=0;
	message(prg,'F',"message line too long");
      }

      /* send the message */
      snprintf(MAXS(tmp),"MSG %s",utf_msg);
      sendheader(sockfd,tmp);
      
    }
  
  } else {  /* single line or chat mode */
   
    do {
      /* read the message */
      *iso_msg=0;
      
      /* message given by -s option? */
      if (*msg) 
	strcpy(iso_msg,msg);
      else { /* prompt for message */
	if (isatty(fileno(stdin))) strcpy(iso_msg,"message: ");
	if (!getpromptline(iso_msg,LEN_ISO-1)) {
	  printf("\n");
	  break;
	}
      }
      
      if (!*iso_msg) break;

      /* strip off new line */
      cp=strrchr(iso_msg,'\n');
      if (cp && (cp!=(char *)iso_msg)) *cp=0;

      /* encode to UTF-7 */
      iso2utf(utf_msg,iso_msg);

      /* send the message */
      snprintf(MAXS(line),"MSG %s",utf_msg);
      sock_putline(sockfd,line);
      xonf=0;
      sr=getreply(sockfd);
      xonf=1;
     
      if (!(str_beq(sr,"200") || str_beq(sr,"202"))) {
        if (strstr(sr,"Timeout")) {
          sockfd=saft_connect("msg",recipient,user,host,tmp);
          sendheader(sockfd,line);
        } else {
          exit(1);
        }
      }

    } while (chat);

  }

  /* close the connection */
  sock_putline(sockfd,"QUIT");
  getreply(sockfd);
  close(sockfd);

  return(0);
}


/* cleanup  - clean termination routine */
void cleanup() {
  printf("\r\n");
}


/*
 * usage - print short help usage text
 */
int usage() {
  fprintf(stderr,"usage: %s [-vfmMr] [-s 'message' ] user[@host]\n",prg);
  fprintf(stderr,"options: -v  verbose mode\n");
  fprintf(stderr,"         -f  force sending of messages and ignore the tty status\n");
  fprintf(stderr,"         -m  allow receiving messages only on this tty\n");
  fprintf(stderr,"         -M  allow receiving messages on other ttys, too\n");
  fprintf(stderr,"         -s  send 'message'\n");
  fprintf(stderr,"         -r  reply to last received message\n");
#ifdef ENABLE_MULTIPROTOCOL
  fprintf(stderr,"         -4  explicitly force ipv4 connections\n");
  fprintf(stderr,"         -6  explicitly force ipv6 connections\n");
#endif
  fprintf(stderr,"example: %s framstag@bofh.belwue.de\n",prg);
  return(2);
}
