#define DEFAULTSERVER  "tramp.cc.utexas.edu"

/* the following are machines known to be POSIX compliant --
   if you have trouble and it works after adding yours, let me know */

#ifdef	_POSIX_SOURCE
/* only linux predefines this for all I know */
#define _POSIX_VERSION
#endif

/*	to make most:	gcc -o tinyirc tinyirc.c -ltermcap
	under aix:	bsdcc -o tinyirc tinyirc.c -lcurses
	under hpux:	cc -o tinyirc tinyirc.c -lcurses

	PLEASE CHANGE THE DEFAULT SERVER TO ONE NEAR YOU
	if you have no idea, start with eff.org (US) or 
	nic.funet.fi (europe), then ask online.

  Send your comments and suggestions for enhancements to Nathan Laredo
  nathan@eas.gatech.edu.  This program falls under the GNU COPYLEFT
  which is available for ftp on prep.ai.mit.edu - please refer to it
  for specific conditions and terms.

Tested and verified working under: SunOS 4.1.3, Ultrix 4.2,
Linux 0.99.2, Dynix 3.1.2.
Some problems in input under: HPUX and IRIX (and see if I care)

ALL 2.7.1g server commands (*=sent to client also):

ADMIN AWAY CONNECT DIE *ERROR HASH HELP INFO *INVITE ISON *JOIN *KICK KILL
LINKS LIST LUSERS *MODE MOTD NAMES *NICK NOTE *NOTICE OPER *PART PASS *PING
PONG *PRIVMSG *QUIT REHASH RESTART SERVER SQUIT STATS SUMMON TIME *TOPIC
TRACE USER USERHOST USERS VERSION WALLOPS WHO WHOIS WHOWAS

*/

#define DEFAULTPORT	6667
#define COMMANDCHAR	'/'
#include <stdio.h>
#ifndef _POSIX_VERSION
#include <sgtty.h>
#define	USE_OLD_TTY
#include <sys/ioctl.h>
#undef	USE_OLD_TTY
#if !defined(sun) && !defined(sequent)
#include <strings.h>
#define strchr index
#ifndef	CBREAK
#define CBREAK RAW
#endif
#else
#include <string.h>
#endif
#else
#include <string.h>
#include <termios.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>

int	sockfd,sok=1,tty_des,CO,LI,_tty_ch;
u_short	IRCPORT=DEFAULTPORT;
char	*CM,*CS,*CE,bp[1024],*term,*ptr,buf[512],object[256],localhost[64];
char	sockbuf[512],inputbuf[512],IRCNAME[10],*token[256],*fromhost;
char	termcap[1024],*ti;
fd_set	readfs;
struct	passwd *userinfo;
#ifdef	_POSIX_VERSION
struct	termios _tty;
tcflag_t _res_iflg, _res_lflg;
#define cbreak() (_tty.c_lflag&=~ICANON, \
	tcsetattr(_tty_ch, TCSANOW, &_tty))
#define noecho() (_tty.c_lflag &= ~(ECHO|ICRNL), \
	tcsetattr(_tty_ch, TCSADRAIN, &_tty))
#define savetty() ((void) tcgetattr(_tty_ch, &_tty), \
	_res_iflg = _tty.c_iflag, _res_lflg = _tty.c_lflag)
#define resetty() (_tty.c_iflag = _res_iflg, _tty.c_lflag = _res_lflg,\
	(void) tcsetattr(_tty_ch, TCSADRAIN, &_tty))
#define erasechar()	(_tty.c_cc[VERASE])
#else
struct	sgttyb _tty;
int	_res_flg;
#define cbreak() (_tty.sg_flags|=CBREAK, ioctl(_tty_ch, TIOCSETP, &_tty))
#define noecho() (_tty.sg_flags &= ~(ECHO|CRMOD), \
	ioctl(_tty_ch, TIOCSETP, &_tty))
#define savetty() ((void) ioctl(_tty_ch, TIOCGETP, &_tty), \
	_res_flg = _tty.sg_flags)
#define resetty() (_tty.sg_flags = _res_flg, \
	(void) ioctl(_tty_ch, TIOCSETP, &_tty))
#define erasechar()	(_tty.sg_erase)
#endif

static	int current=0; /* current position in input buffer */
static	int pos=0; /* position on input line */
static	int poslc=0; /* current "page" of 70 columns */
static	time_t idletimer; /* idle time for user */
int	putchar_x(c) int c; { putchar(c); }

#define	tputs_x(s) (tputs(s,0,putchar_x))
int call_socket(hostname)
  char *hostname;
{
  struct sockaddr_in sa;
  struct hostent     *hp;
  int    a, s;
  if ((hp=gethostbyname(hostname))==NULL) {
    errno=ECONNREFUSED;
    return(-1);
  }
  bzero(&sa, sizeof(sa));
  bcopy(hp->h_addr, (char *)&sa.sin_addr, hp->h_length);
  sa.sin_family = hp->h_addrtype;
  sa.sin_port = htons((u_short)IRCPORT);
  if((s=socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
    return(-1);
  if(connect(s,(struct sockaddr *) &sa, sizeof(sa)) < 0) {
    close(s);
    return(-1);
  }
  return(s);
}

int readln()
{
  int i=0,valid=1;
  char c;
  while(valid) {
  if (read(sockfd,&c,1)<1) return(0);
  if(i<511 && c != '\n') sockbuf[i++]=c; else valid=0; }
  sockbuf[i]='\0';
  return(1);
}

updatestatus()
{ time_t now;
  tputs_x(tgoto(CM, 0, LI-2)); now=time(NULL);
  *(strchr(ti=ctime(&now),'\n'))='\0';
  printf("[%s] %s on %s : TinyIRC 0.4%s",ti,
	IRCNAME,object,CE);
}

int writeln(outbuf)
char *outbuf;
{
  int to=0;
  if( write(sockfd, outbuf, strlen(outbuf)) < to )
	return(0);
  return(1);
}

finishline(start)
int start; { int i; i=start; while (token[i]) printf(" %s",token[i++]); }

dojoin()
{
  if(strcmp(token[0],IRCNAME)==0) {
	printf("*** Current channel is %s",token[2]);
	strcpy(object,token[2]);
	updatestatus();
	}
  else printf("*** %s (%s) joined %s",token[0],fromhost,token[2]);
}

dopart()
{
  if(strcmp(token[0],IRCNAME)==0) { 
    if(strcmp(object,token[2])==0) strcpy(object,"*");
	printf("*** You have left %s",token[2]);
	updatestatus();
	}
  else printf("*** %s (%s) has left channel %s",token[0],fromhost,
	token[2]);
}

donick()
{
  if(strcmp(token[0],IRCNAME)==0) { strcpy(IRCNAME,token[2]);
	printf("*** You have changed your nickname to %s", token[2]);
	updatestatus();
	}
  else printf("*** %s is now known as %s",token[0],token[2]);
}

doprivmsg()
{ int i=4;
  int noctcp=0;
    if(*(++token[3])=='\01') { /* ctcp reply */
      if(*(token[3]+1)=='A') {
      printf("*** ACTION: %s",token[0]); noctcp=1; }
      else printf("*** got CTCP %s from %s",++token[3], token[0]); 
    } /* ctcp something */
    else {
    if(*token[2] != '#') {
    printf("*%s*",token[0]); i=3; noctcp=1; }
    else { 
      if (strcmp(object,token[2])!=0) printf("<%s:%s>",token[0],token[2]);
	else printf("<%s>",token[0]); 
	i=3; noctcp=1; }
    } /* private message to channel or user */
    finishline(i);
    if(noctcp) return;
    if(*token[3]=='V') {
      sprintf(buf,"NOTICE %s \01VERSION TinyIRC 0.4 *IX :12596 bytes\01\n",
	token[0]);
      writeln(buf); }
    else if(*token[3]=='P') {
      sprintf(buf,"NOTICE %s \01PID %d\01\n",token[0],getpid());
      writeln(buf); }
    else if(*token[3]=='F') {
      sprintf(buf,"NOTICE %s \01FINGER %s (%s@%s) Idle %d seconds\01\n",
	token[0],userinfo->pw_gecos,userinfo->pw_name,localhost,
	time(NULL)-idletimer);
      writeln(buf); }
    else if(*token[3]=='C') {
      sprintf(buf,"NOTICE %s \01CLIENTINFO %s\01\n", token[0],
	"VERSION FINGER CLIENTINFO PID ERRMSG no dcc yet...");
      writeln(buf); }
    else if(*token[3]=='E') { /* ERRMSG, hopefully echos pings... */
      sprintf(buf,"NOTICE %s \01%s %s %s %s\n", token[0],
	token[3],token[4],token[5],token[6]); /* big kludge */
      writeln(buf); }
    else if(*token[3]=='D') {
      sprintf(buf,"NOTICE %s \01DCC not supported\01\n",token[0]);
      writeln(buf); }
    else { /* whatever I didn't think of */
      sprintf(buf,"NOTICE %s \01ERRMSG %s\01\n",token[0],
	"I'm sorry dave, I'm afraid I can't do that");
      writeln(buf); } /* end of CTCP half-assed effort */
} /* privmsg */

donotice()
{ int j=3;
    if(*(++token[3])=='\01') /* ctcp reply */
      printf("*** CTCP %s reply from %s:",++token[j++],token[0]);
    else if (strchr(token[0],'.')==0) printf("-%s-",token[0]);
    finishline(j);
} /* notice */

spitout() /* parse lines from server and output */
{ int i;
  int num;
  char *temp;

  if (strncmp(sockbuf,"PI",2)==0) { strncpy(sockbuf,"PO",2);
    return(writeln(strcat(sockbuf,"\n"))); /* ping - pong */
  }
  tputs_x(tgoto(CM,0,LI-3));
  token[i=0]=strtok(sockbuf," ");
  while(token[++i]=strtok(NULL, " ")); token[i]=NULL;
  if(*token[0] != ':') { /* old-style NOTICE from server usually */
    finishline(0); printf("\n"); return(0);
    }
  else token[0]++; /* ok, make a lot of assumptions here */
  if(temp=strchr(token[0],'!')) { *temp='\0'; fromhost=temp+1; }
  if(num=atoi(token[1])) /* feel free to extend this one */
	switch(num) {
	case 352: /* rpl_whoreply */
	  printf("%-15s %-10s %3s %s@%s",token[3],token[7],
		token[8],token[4],token[5]);
	  finishline(9);
	  break;
	default:
	  printf("%s",token[1]);
	  finishline(3);
	  break;
	}
  else if(*token[1]=='P')
	if(*(token[1]+1)=='R') doprivmsg();
	else dopart();
  else if(*token[1]=='N')
	if(*(token[1]+1)=='O') donotice();
	else donick();
  else if(*token[1]=='J') dojoin();
  else if(*token[1]=='Q') {
    printf("*** signoff (%s)",token[0]); finishline(2); }
  else if(*token[1]=='T') {
    printf("*** %s has changed the topic on %s to",token[0],token[2]);
    finishline(3); }
  else if(*token[1]=='I')
    printf("*** %s has invited you to join channel %s",token[0],token[3]);
  else if(*token[1]=='M') {
    printf("*** Mode change on %s by %s to \"%s",token[2],token[0],token[3]);
    finishline(4); printf("\""); }
  else if(*token[1]=='K')
    printf("*** %s kicked %s from %s",token[0], token[3], token[2]);
  else if(*token[1]=='E') {
    printf("*** ERROR:"); finishline(2); }
  else /* if all else fails */
  { printf("***"); finishline(0); }
printf("\n"); /* blank line at this point means code sucks */
fflush(stdout);
}

int parseinput()
{ char *temp;

  tputs_x(tgoto(CM,0,LI-1)); tputs_x(CE); tputs_x(tgoto(CM,0,LI-3));
  inputbuf[current]='\0';
  current=0;
  if (inputbuf[0]==COMMANDCHAR) {
    printf("* %s",inputbuf);
    if (inputbuf[1] != '?') writeln(&inputbuf[1]);
      else {
	if(temp=strchr(inputbuf,'\n')) *temp='\0'; /* remove newline */
	printf("*** Default object set to %s\n",
	strcpy(object,&inputbuf[2])); updatestatus(); }
    }
  else {
	sprintf(buf,"PRIVMSG %s %s",object,inputbuf);
	writeln(buf); printf("> %s",inputbuf); }
  idletimer=time(NULL);
  fflush(stdout);
}

takeinchar()
{ char ch;
  ch=getchar();
  if (current < 500) inputbuf[current++]=ch;
  if ( ch=='\10' || ch=='\177' || ch==erasechar() ) {
	if (pos) { current -= 2; --pos; }
	printf("%c %c",erasechar(),erasechar());
	if(!pos && poslc) printf("%s",&inputbuf[(--poslc)*(pos=70)]);
	}
  else if (ch != '\r' && ch != '\n') {
	putchar(ch);
	if((++pos)==70) {
	  tputs_x(tgoto(CM, pos=0, LI-1)); tputs_x(CE); poslc++; }
	}
	else { pos=0; poslc=0; inputbuf[current-1]='\n'; if (current>1)
		parseinput(); }
}

void exit_cleanup() /* restore terminal on any exit signal */
{ tputs_x(tgoto(CS,-1,-1)); tputs_x(tgoto(CM,0,LI-1)); resetty(); exit(128); }

main(argc, argv)
  int argc;
  char **argv;
{ char hostname[64];
  int i, errflag;
  extern int optind, opterr;

userinfo = getpwuid(getuid());
if (!getenv("IRCNICK"))
    strncpy(IRCNAME,userinfo->pw_name,sizeof(IRCNAME));
    else strncpy(IRCNAME,(char *) getenv("IRCNICK"), sizeof(IRCNAME));
strcpy(hostname,DEFAULTSERVER);
if(argc>1) {
  if (argv[1][0]=='-' || argc>4) { /* assume "-" means want help */
    fprintf(stderr,"usage: %s [nick] [server] [port]\n", argv[0]);
    exit(1); }
  for (i=1; i < argc; i++)
  if (strchr(argv[i],'.')) strcpy(hostname,argv[i]);
	else if (atoi(argv[i])) IRCPORT=atoi(argv[i]);
	   else strcpy(IRCNAME,argv[i]);
  }
  gethostname(localhost, 64);
  printf("*** trying port %d of %s\n\n\n",IRCPORT,hostname);
  if ((sockfd=call_socket(hostname))==-1) {
    fprintf(stderr, "*** connection refused, aborting\n", hostname);
    exit(0);
  }
  sprintf(buf,"NICK %s\n",IRCNAME);
  writeln(buf);
  if (!getenv("IRCNAME")) 
  sprintf(buf, "USER %s * * %s\n", userinfo->pw_name,userinfo->pw_gecos);
  else
  sprintf(buf, "USER %s * * %s\n", userinfo->pw_name,getenv("IRCNAME"));
  writeln(buf);
  strcpy(object,"*");
  if((term=(char *)getenv("TERM"))==NULL) {
	fprintf(stderr, "tinyirc: TERM not set\n");
	exit(1);
	}
  if(tgetent(bp, term) < 1) {
	fprintf(stderr, "tinyirc: no termcap entry for %s\n",term);
	exit(1);
	}
  if((CO=tgetnum("co")) == -1) CO=80;
  if((LI=tgetnum("li")) == -1) LI=24;
  ptr = termcap;
  if((CM=(char *)tgetstr("cm", &ptr))==NULL)
	CM=(char *)tgetstr("cl", &ptr);
  if(!CM || !(CS=(char *)tgetstr("cs", &ptr)) ||
	!(CE=(char *)tgetstr("ce", &ptr))) {
	printf("tinyirc: sorry, your terminal needs cm/cl,cs,ce\n");
	exit(1);
	}
  if ((_tty_ch = open("/dev/tty", O_RDWR, 0)) == -1) _tty_ch = 0;
  signal(SIGINT,exit_cleanup); signal(SIGHUP,exit_cleanup);
  signal(SIGKILL,exit_cleanup); savetty(); cbreak(); noecho();
  tputs_x(tgoto(CS,LI-3,0)); idletimer=time(NULL); updatestatus();
  while(sok) {
	FD_ZERO(&readfs); FD_SET(sockfd,&readfs);
	FD_SET(fileno(stdin),&readfs);
  	tputs_x(tgoto(CM,pos,LI-1)); fflush(stdout); fflush(stdin);
	if(select(FD_SETSIZE, &readfs, NULL, NULL, NULL)) {
	  if(FD_ISSET(fileno(stdin),&readfs)) takeinchar();
	  if(FD_ISSET(sockfd,&readfs)) {  sok = readln(); 
	    if (sok) spitout(); }
	  }
  } /* while socket is ok */
  tputs_x(tgoto(CS,-1,-1)); tputs_x(tgoto(CM,0,LI-1)); resetty();
} /* main */
