#define DEFAULTSERVER	"patriot.mit.edu"
#define DEFAULTPORT	6667
#define COMMANDCHAR	'/'
#define TINYSIZE 12322
/* the following are machines known to be POSIX compliant --
   if you have trouble and it works after adding yours, let me know */

#ifdef	linux
#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
*/
#include <stdio.h>
#ifndef _POSIX_VERSION
#include <sgtty.h>
#define	USE_OLD_TTY
#include <sys/ioctl.h>
#undef	USE_OLD_TTY
#ifndef	CBREAK
#define CBREAK RAW
#endif
#if !defined(sun) && !defined(sequent) && !defined(hpux) && \
	!defined(_AIX) && !defined(aix)
#include <strings.h>
#define strchr index
#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,dumb=0;
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	timeval timeout;
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;
if(!dumb) {
  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,noctcp=0;
if(*(++token[3])=='\01') {
	if(*(token[3]+1)=='A') {
	printf("*** ACTION: %s",token[0]); noctcp=1; }
	else printf("*** got CTCP %s from %s",++token[3], token[0]); 
	}
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; }
	}
finishline(i); if(noctcp) return;
/* CTCP requests */
if(*token[3]=='V') {
	sprintf(buf,"NOTICE %s \01VERSION TinyIRC 0.4 *IX :%d bytes\01\n",
	token[0], TINYSIZE ); 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"); writeln(buf); }
else if(*token[3]=='E') { sprintf(buf,"NOTICE %s \01%s %s %s %s\n", token[0],
	token[3],token[4],token[5],token[6]); writeln(buf); }
else if(*token[3]=='D') { sprintf(buf,"NOTICE %s \01DCC not supported\01\n",
	token[0]); writeln(buf); }
else { sprintf(buf,"NOTICE %s \01ERRMSG %s\01\n",token[0],
	"I'm sorry dave, I'm afraid I can't do that"); writeln(buf); }
}

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);
}

spitout()
{ int i,num;
  char *temp;

if(strncmp(sockbuf,"PI",2)==0) { strncpy(sockbuf,"PO",2);
	return(writeln(strcat(sockbuf,"\n"))); } /* ping - pong */
/* parse lines from server for output to terminal */
if(!dumb) tputs_x(tgoto(CM,0,LI-3));
token[i=0]=strtok(sockbuf," ");
while(token[++i]=strtok(NULL, " ")); token[i]=NULL;
if(*token[0] != ':') { finishline(2); putchar('\n'); return(0); }
	else token[0]++;
if(temp=strchr(token[0],'!')) { *temp='\0'; fromhost=temp+1; }
if(num=atoi(token[1])) /* this is quite extensible, ya know... */
	switch(num) { /* server-numerics */
	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("*** %s quit",token[0]); finishline(2); }
else if(*token[1]=='T') {
	printf("*** %s changed the %s topic to",token[0],token[2]);
	finishline(3); }
else if(*token[1]=='I') printf("*** %s invited you to %s",token[0],token[3]);
else if(*token[1]=='M') {
	printf("*** %s changed %s to \"%s",token[0],token[2],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 { printf("*** odd server stuff:"); finishline(0); }
putchar('\n'); if(!dumb) fflush(stdout);
}

int parseinput()
{ int i=1;
if(!dumb) {
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) {
  switch(toupper(inputbuf[1])) {
	case 'Q': if(strlen(inputbuf) < 7) {
		strcat(inputbuf,":tinyirc 0.4 - exit\n");
		*strchr(inputbuf,'\n')=' '; }
		break;
	case 'M': if(inputbuf[2]==' ') { writeln("PRIVMSG");
		writeln(&inputbuf[2]); printf("->%s",&inputbuf[2]); i=0; }
		break;
	case '?': *strchr(inputbuf,'\n')='\0';
		printf("*** Default object set to %s\n",
		strcpy(object,&inputbuf[2])); updatestatus(); i=0;
		break;
	default: break; /* this is where you'd match commands */ }
  if(i) { writeln(&inputbuf[1]); printf("= %s",inputbuf); } }
else {
	sprintf(buf,"PRIVMSG %s %s",object,inputbuf);
	writeln(buf); printf("> %s",inputbuf); }
idletimer=time(NULL); if (!dumb) fflush(stdout);
}

takeinchar()
{ char ch;
if(dumb) {
	fgets(inputbuf,511,stdin); parseinput(); }
else {
  ch=getchar();
  if(current<500) inputbuf[current++]=ch;
  if(ch=='\10' || ch=='\177' || ch==erasechar()) {
	if (pos) { inputbuf[current-=2]='\0'; --pos; }
	printf("%c %c",ch,ch);
	if(!pos && poslc) printf("%s",&inputbuf[(--poslc)*(pos=70)]);
	}
  else if (ch != '\r' && ch != '\n') {
	putchar(ch);
	if((++pos)>71) {
	  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(); }
  } /* not dumb mode */
}

void cleanup()
{ 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;

userinfo = getpwuid(getuid()); strcpy(hostname,DEFAULTSERVER);
if (!getenv("IRCNICK")) strncpy(IRCNAME,userinfo->pw_name,sizeof(IRCNAME));
	else strncpy(IRCNAME,(char *) getenv("IRCNICK"), sizeof(IRCNAME));
if(argc>1)
for (i=1; i<argc; i++) if(argv[i][0]=='-') { if(argv[i][1]=='d') dumb=1;
	 	else { fprintf(stderr,"usage: %s [nick] [server] [port] [-d]\n",
			argv[0]); exit(1); } }
	else if(strchr(argv[i],'.')) strcpy(hostname,argv[i]);
		else if(atoi(argv[i])) IRCPORT=atoi(argv[i]);
			else strcpy(IRCNAME,argv[i]);
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); gethostname(localhost, 64);
if (!getenv("IRCNAME")) sprintf(buf, "USER %s %s %s :%s\n", userinfo->pw_name,
	localhost,hostname,userinfo->pw_gecos);
else sprintf(buf, "USER %s %s %s :%s\n", userinfo->pw_name,localhost,
	hostname,getenv("IRCNAME"));
writeln(buf); strcpy(object,"*"); idletimer=time(NULL);
if(!dumb) {
ptr=termcap;
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;
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, no termcap cm/cl,cs,ce: dumb mode set\n");
	dumb=1; }
if(!dumb) {
if ((_tty_ch = open("/dev/tty", O_RDWR, 0)) == -1) _tty_ch = 0;
signal(SIGINT,cleanup); signal(SIGHUP,cleanup); signal(SIGKILL,cleanup);
savetty(); cbreak(); noecho(); tputs_x(tgoto(CS,LI-3,0)); updatestatus(); }
}
while(sok) {
	FD_ZERO(&readfs); FD_SET(sockfd,&readfs);
	FD_SET(fileno(stdin),&readfs);
  	if(!dumb) { tputs_x(tgoto(CM,pos,LI-1)); fflush(stdout);
	timeout.tv_sec=15; timeout.tv_usec=0; }
	if(select(FD_SETSIZE, &readfs, NULL, NULL,(dumb ? NULL : &timeout))) {
		if(FD_ISSET(fileno(stdin),&readfs)) takeinchar();
		if(FD_ISSET(sockfd,&readfs)) {  sok = readln(); 
			if (sok) spitout(); }
	} else updatestatus(); }
if(!dumb) { tputs_x(tgoto(CS,-1,-1)); tputs_x(tgoto(CM,0,LI-1)); resetty(); }
}
/* EOF */
