[comp.sources.misc] yet another chat program

br@uunet.UU.NET@laura.irb.informatik.uni-dortmund.de.UUCP (Bodo Rueskamp) (10/05/87)

[I may have to declare a moratorium on chat programs, the way r$ had to on
editors.  ++bsa]

I wrote a chat program several weeks ago. This chat
program uses sockets to communicate, not named pipes.
I think that this program is useful for others, do
i want you to post it into comp.sources.unix.
[this was forwarded from r$, I take it the answer was no.  ++bsa]
	Bodo Rueskamp

br@exunido.uucp
...!seismo!exunido!br

- -------- cut here ---------------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	chatd.c
#	chat.c
# This archive created: Tue Sep  1 18:45:50 1987
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'

	Chat - A Multi-User Communications System for BSD 4.2

	written by Bodo Rueskamp (br@exunido.uucp)



Installation:

	- create an entry for "chat/tcp" in /etc/services
	- change HOSTNAME in chat.c to chatd's host

Server Commands:

	/bye			signoff from chat
	/channel <ch #>		change channel
	/invite <nick>		invite <nick> to your channel
	/list			list active channels
	/help			print help
	/signon <nick> <ch #>	signon to chat

Client Commands (line edit):

	^D		exit from chat
	CR/LF		send line to chat server
	^X		erase line
	BS/DEL		erase char

SHAR_EOF
fi
if test -f 'chatd.c'
then
	echo shar: "will not over-write existing file 'chatd.c'"
else
cat << \SHAR_EOF > 'chatd.c'

/*
**	Chat Server
**
**	written by Bodo Rueskamp  (...!seismo!exunido!br)
*/


#define Welcome "* Welcome to CHAT v1.01  11-Aug-87\n"


#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <strings.h>
#include <time.h>


int sock;  /*  path number of chatd socket  */
int rmask;  /*  bit masks of active paths  */
char *argerr = "* Invalid arguments.\n";
char *invcmd = "* Invalid command, try /help.\n";
char *notsig = "* You are not signed on.\n";
char *alrsig = "* You are already signed on.\n";
char *invchn = "* Invalid channel number.\n";
char *nonick = "* No nickname.\n";
char *nfound = "* Nickname not found.\n";
struct user_rec {
	char flag;	/*  0 = unused			*/
			/*  1 = waiting for name	*/
			/*  2 = waiting for host	*/
			/*  3 = signed off		*/
			/*  4 = signed on		*/
	int channel;	/*  channel number		*/
	char name[9];	/*  username			*/
	char host[16];	/*  hostname			*/
	char nick[11];  /*  nickname			*/
	int path;	/*  socket path number		*/
	char buf[257];  /*  input buffer		*/
} usr[32];


main()  /*  setup & main loop  */
{
	struct sockaddr_in saddr;
	struct servent *sp;
	int i, rfd, wfd = 0, xfd = 0;

	/*  prepare socket  */
	bzero((char *) &saddr, sizeof(saddr));
	if ((sp = getservbyname("chat", "tcp")) == NULL) {
		fprintf(stderr, "chatd: chat/tcp unknown service\n");
		exit(1);
	}
	saddr.sin_family = AF_INET;
	saddr.sin_port = sp->s_port;
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		fprintf(stderr, "chatd: socket() failed, errno = %d\n", errno);
		exit(1);
	}
	if (bind(sock, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) {
		fprintf(stderr, "chatd: bind() failed, errno = %d\n", errno);
		exit(1);
	}
	(void) listen(sock, 10);
	for (i=0; i<32; ++i)
		usr[i].flag == 0;  /*  no user connected  */
	rmask = 1 << sock;  /*  listen only to chatd socket  */
	for (;;) {
		rfd = rmask;
		(void) select(32, &rfd, &wfd, &xfd, (struct timeval *) 0);  /*  wait for action  */
		if ((rfd >> sock) & 1)  /*  got connection  */
			NewUser();  /*  handle new user  */
		for (i=0; i<32; ++i)  /*  scan each users path  */
			if (usr[i].flag && ((rfd >> usr[i].path) & 1))
				ServeUser(i);
	}
}


NewUser()  /*  handle new user  */
{
	char *MaxMsg = "* User limit reached\n";
	int path;  /*  path to new user  */
	int i;

	/*  accept connection  */
	if ((path = accept(sock, (struct sockaddr *) 0, (int *) 0)) == -1) {
		fprintf(stderr, "chatd: accept() failed, errno = %d\n", errno);
		exit(1);
	}
	/*  send welcome, exit if welcome failed  */
	if (write(path, Welcome, sizeof(Welcome)-1) != sizeof(Welcome)-1) {
		(void) close(path);
		return;
	}
	for (i=0; i<32; ++i)  /*  search for free entry  */
		if (usr[i].flag == 0)
			break;
	if ((i == 32) || (path > 31)) {
		(void) write(path, MaxMsg, sizeof(MaxMsg)-1);  /*  send "sorry"  */
		(void) close(path);
		return;
	}
	/*  prepare entry for new user  */
	usr[i].flag = 1;
	usr[i].path = path;
	usr[i].name[0] = '\0';
	usr[i].host[0] = '\0';
	usr[i].nick[0] = '\0';
	rmask |= 1 << path;  /*  listen to this path too  */
	return;
}


LostUser(i)  /*  lost connection to user  */
int i;  /*  number of user  */
{
	char buf[256];

	(void) close(usr[i].path);  /*  close path  */
	if (usr[i].flag == 4) {
		(void) sprintf(buf, "|signoff| %s@%s (%s)\n",
			usr[i].name, usr[i].host, usr[i].nick);
		BroadCast(usr[i].channel, buf, i);
	}
	usr[i].flag = 0;  /*  free entry  */
	rmask &= ~(1 << usr[i].path);  /*  don't listen to close path  */
}


ServeUser(i)  /*  get & analyze line from user  */
int i;  /*  number of user  */
{
	char buf[256], bf[256];

	if (GetLine(usr[i].path, buf) == 0) {  /*  get line  */
		LostUser(i);
	} else switch(usr[i].flag) {
		case 1:
			if ((strlen(buf) > 10) ||
			    (sscanf(buf, "u=%s", usr[i].name) != 1)) {
				LostUser(i);
			} else {
				usr[i].flag = 2;
			}
			break;
		case 2:
			if ((strlen(buf) > 17) ||
			    (sscanf(buf, "h=%s", usr[i].host) != 1)) {
				LostUser(i);
			} else {
				usr[i].flag = 3;
			}
			break;
		case 3:
			if (buf[0] == '/') {
				Command(i, buf+1);
			} else {
				ToUser(i, notsig);
			}
			break;
		case 4:
			if (buf[0] == '/') {
				Command(i, buf+1);
			} else {
				(void) sprintf(bf, "<%s> %s", usr[i].nick, buf);
				BroadCast(usr[i].channel, bf, i);
			}
			break;
	}
}


GetLine(path, buf)  /*  get line  */
int path;  /*  path number  */
char *buf;  /*  pointer to buffer  */
{
	register int i;
	char c;

	i = 0;
	while (i < 79) {
		if (read(path, &c, 1) != 1)
			return(0);
		buf[i++] = c;
		if (c == '\n')
			break;
	}
	buf[i] = '\0';
	return(1);
}


BroadCast(channel, msg, user)  /*  broadcast message to users on channel  */
int channel;  /*  channel number  */
char *msg;  /*  pointer to message  */
int user;  /*  number of user, who sent the message */
{
	register int i;

	for (i=0; i<32; ++i)
		if ((usr[i].flag == 4) &&
		    (i != user) &&
		    (usr[i].channel == channel))
			ToUser(i, msg);
}


Command(num, msg)  /*  execute command for user  */
int num;  /*  number of user  */
char *msg;  /*  pointer to command from user  */
{
	int argc;
	char *argv[5];
	register char *p;
	int i, count[200];
	char buf[256], cnum[3];

	argc = 0;
	for (p=msg; *p;) {
		if (argc == 5) {
			ToUser(num, invcmd);
			return;
		}
		argv[argc++] = p;
		while ((*p != '\0') && (*p != '\n') && (*p != ' '))
			++p;
		if (*p != '\0')
			*p++ = '\0';
		while ((*p == '\n') || (*p == ' '))
			++p;
	}
	if (strncmp("bye", argv[0], strlen(argv[0])) == 0) {
		if (argc > 1) {
			ToUser(num, argerr);
			return;
		}
		if (usr[num].flag == 4) {
			(void) sprintf(buf, "|signoff| %s@%s (%s)\n",
				usr[num].name, usr[num].host, usr[num].nick);
			BroadCast(usr[num].channel, buf, -1);
		}
		if (usr[num].flag) {
			(void) close(usr[num].path);
			usr[num].flag = 0;
		}
		return;
	}
	if (strncmp("channel", argv[0], strlen(argv[0])) == 0) {
		if (argc != 2) {
			ToUser(num, argerr);
			return;
		}
		if ((sscanf(argv[1], "%d", &i) != 1) || (i < 0) || (i > 199)) {
			ToUser(num, invchn);
			return;
		}
		(void) sprintf(buf, "|change| %s@%s (%s) has left this channel\n",
			usr[num].name, usr[num].host, usr[num].nick);
		BroadCast(usr[num].channel, buf, num);
		usr[num].channel = i;
		(void) sprintf(buf, "* You are now on channel %d\n", i);
		ToUser(num, buf);
		if (usr[num].flag == 0)
			return;
		(void) sprintf(buf, "|change| %s@%s (%s) has joined this channel\n",
			usr[num].name, usr[num].host, usr[num].nick);
		BroadCast(usr[num].channel, buf, num);
		return;
	}
	if (strncmp("help", argv[0], strlen(argv[0])) == 0) {
		if (argc > 1) {
			ToUser(num, argerr);
			return;
		}
		PrintHelp(num);
		return;
	}
	if (strncmp("invite", argv[0], strlen(argv[0])) == 0) {
		if (usr[num].flag != 4) {
			ToUser(num, notsig);
			return;
		}
		if (argc != 2) {
			ToUser(num, nonick);
			return;
		}
		for (i=0; i<32; ++i)
			if (strcmp(argv[1], usr[i].nick) == 0)
				break;
		if (i == 32) {
			ToUser(num, nfound);
			return;
		}
		(void) sprintf(buf, "* %s invites you to channel %d\n",
			usr[num].nick, usr[num].channel);
		ToUser(i, buf);
		return;
	}
	if (strncmp("list", argv[0], strlen(argv[0])) == 0) {
		if (argc > 1) {
			ToUser(num, argerr);
			return;
		}
		for (i=0; i<200; ++i)
			count[i] = 0;
		for (i=0; i<32; ++i)
			if (usr[i].flag == 4)
				++count[usr[i].channel];
		ToUser(num, "* Ch Users\n");
		if (usr[num].flag == 0)
			return;
		for (i=0; i<200; ++i) {
			if (count[i] == 0)
				continue;
			if (i < 100) {
				(void) sprintf(cnum, "%2d", i);
			} else {
				(void) strcpy(cnum, "??");
			}
			(void) sprintf(buf, "* %s %d\n", cnum, count[i]);
			ToUser(num, buf);
			if (usr[num].flag == 0)
				return;
		}
		ToUser(num, "*\n");
		return;
	}
	if (strncmp("names", argv[0], strlen(argv[0])) == 0) {
		if (argc > 1) {
			ToUser(num, argerr);
			return;
		}
		ToUser(num, "*\n* Ch Nickname   UserID@Node\n");
		if (usr[num].flag == 0)
			return;
		for (i=0; i<32; ++i) {
			if (usr[i].flag != 4)
				continue;
			if (usr[i].channel < 100) {
				(void) sprintf(cnum, "%2d", usr[i].channel);
			} else {
				(void) strcpy(cnum, "??");
			}
			(void) sprintf(buf, "* %s %-10s %s@%s\n",
				cnum, usr[i].nick, usr[i].name, usr[i].host);
			ToUser(num, buf);
			if (usr[num].flag == 0)
				return;
		}
		ToUser(num, "*\n");
		return;
	}
	if (strncmp("signon", argv[0], strlen(argv[0])) == 0) {
		if (usr[num].flag != 3) {
			ToUser(num, alrsig);
			return;
		}
		if (argc > 1) {
			if (strlen(argv[1]) > 10)
				argv[1][10] = '\0';
			(void) strcpy(usr[num].nick, argv[1]);
		} else {
			(void) strcpy(usr[num].nick, usr[num].name);
		}
		if (argc > 2) {
			if (sscanf(argv[2], "%d", &usr[num].channel) != 1) {
				ToUser(num, invchn);
				return;
			}
		} else {
			usr[num].channel = 1;
		}
		usr[num].flag = 4;
		(void) sprintf(buf, "|signon| %s@%s (%s)\n",
			usr[num].name, usr[num].host, usr[num].nick);
		BroadCast(usr[num].channel, buf, -1);
		return;
	}
	ToUser(num, invcmd);
}


PrintHelp(i)  /*  send help to user  */
int i;  /*  user number  */
{
	static char *msg[] = {
"*\n",
"* Chat Help:\n",
"*   /bye                              signoff from chat\n",
"*   /channel <channel #>              change channel\n",
"*   /invite <nick>                    invite <nick> to your channel\n",
"*   /list                             list active channels\n",
"*   /help                             print help\n",
"*   /signon <nick> <channel #>        signon to chat\n",
"*\n",
		NULL};
	int line;

	for (line=0; usr[i].flag && msg[line]; ++line) {
		ToUser(i, msg[line]);
	}
}


ToUser(i, msg)  /*  send line to user  */
int i;  /*  user number  */
char *msg;  /*  pointer to line  */
{
	if (write(usr[i].path, msg, strlen(msg)) != strlen(msg))
		LostUser(i);
}

SHAR_EOF
fi
if test -f 'chat.c'
then
	echo shar: "will not over-write existing file 'chat.c'"
else
cat << \SHAR_EOF > 'chat.c'

/*
**	Chat Client
**
**	written by Bodo Rueskamp (...!seismo!exunido!br(
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>

#define HOSTNAME "laura"  /*  name of host, where chat daemon lives  */

struct sockaddr_in sin;
struct hostent *hp;
struct servent *sp;
int s, pos, lines, idx;
char inbuf[62];
char hostname[16], buf[64];
char tc_entry[1024], PC, *UP, *BC, *CM, *CL, *CE, *tgoto();


outch(c)  /*  send byte to screen  */
char c;
{
	swrite(1, &c, 1);
}


quit()  /*  exit  */
{
	(void) system("stty echo -raw");
	tputs(tgoto(CM, 0, lines-1), 1, outch);
	printf("\n\007Connection closed.\n");
	exit(-1);
}


swrite(path, buf, len)  /*  write with error checking  */
int path;  /*  path number  */
char *buf;  /*  pointer to string  */
int len;  /*  length of string  */
{
	if (write(path, buf, len) != len)
		quit();
}


wstring(str, update)  /*  write string to user  */
char *str;  /*  pointer to string  */
int update;  /*  if 1: update top line  */
{
	register char *p;
	int flag = 0;

	tputs(tgoto(CM, pos, lines-1), 1, outch);  /*  goto last line  */
	for (p=str; *p; ++p) {
		swrite(1, p, 1);
		++pos;
		if (*p == '\n') {
			flag = 1;
			tputs(tgoto(CM, 0, lines-1), 1, outch);
			pos = 0;
		}
	}
	if (flag && update) {
		tputs(tgoto(CM, 0, 0), 1, outch);
		tputs(CE, 1, outch);
		swrite(1, "-> ", 3);
		inbuf[idx] = '\0';
		swrite(1, inbuf, strlen(inbuf));
	}
}


term()
{
	char c;
	int rfd, wfd = 0, xfd = 0, rmask = (1 << s) | (1 << 0);
	char buf[256], *p;
	char *termcap, tcbuff[256], *tcbuffp = tcbuff, *tgetstr(), *getenv();
	int len, columns;

	if ((termcap = getenv("TERM")) == NULL) {
		fprintf(stderr, "no TERM environment variable\n");
		exit(1);
	}
	if (tgetent(tc_entry, termcap) != 1) {
		fprintf(stderr, "unable to get '%s' capabilities\n", termcap);
		exit(1);
	}
	if ((lines = tgetnum("li")) == -1)
		lines = 24;
	if (lines < 5) {
		fprintf(stderr, "terminal must have at least five lines\n");
		exit(1);
	}
	if ((columns = tgetnum("co")) == -1)
		columns = 80;
	if (columns < 80) {
		fprintf(stderr, "terminal must have at least 80 columns\n");
		exit(1);
	}
	p = tgetstr("pc", &tcbuffp);
	PC = (p != NULL) ? *p : ' ';
	if ((UP = tgetstr("up", &tcbuffp)) == NULL) {
		fprintf(stderr, "terminal hasn't UP capability\n");
		exit(1);
	}
	BC = tgetstr("bc", &tcbuffp);
	if ((CM = tgetstr("cm", &tcbuffp)) == NULL) {
		fprintf(stderr, "terminal hasn't CM caiability\n");
		exit(1);
	}
	if ((CL = tgetstr("cl", &tcbuffp)) == NULL) {
		fprintf(stderr, "terminal hasn't CL capability\n");
		exit(1);
	}
	if ((CE = tgetstr("ce", &tcbuffp)) == NULL) {
		fprintf(stderr, "terminal hasn't CE capability\n");
		exit(1);
	}
	(void) system("stty -echo raw");
	tputs(CL, lines, outch);
	swrite(1, "->", 2);
	idx = 0;
	pos = 0;
	for (;;) {
		tputs(tgoto(CM, idx+3, 0), 1, outch);
		rfd = rmask;
		(void) select(s+1, &rfd, &wfd, &xfd, (struct timeval *) 0);
		if ((rfd >> 0) & 1) {
			if (read(0, &c, 1) != 1)
				quit();
			switch(c) {
				case 0x04:
					quit();
				case 0x08:
				case 0x7f:
					if (idx) {
						--idx;
						swrite(1, BC, strlen(BC));
					}
					break;
				case 0x18:
					idx = 0;
					tputs(tgoto(CM, 3, 0), 1, outch);
					tputs(CE, 1, outch);
					break;
				default:
					swrite(1, &c, 1);
					inbuf[idx++] = c;
					if (idx < 60)
						break;
				case 0x0a:
				case 0x0d:
					inbuf[idx++] = '\n';
					inbuf[idx] = '\0';
					if (inbuf[0] != '\n')
						swrite(s, inbuf, strlen(inbuf));
					idx = 0;
					wstring(inbuf, 0);
					tputs(tgoto(CM, 3, 0), 1, outch);
					tputs(CE, 1, outch);
					break;
			}
		}
		if ((rfd >> s) & 1) {
			if ((len = read(s, buf, sizeof(buf)-1)) < 1)
				quit();
			buf[len] = '\0';
			wstring(buf, 1);
		}
	}
}


main()
{
	bzero(&sin, sizeof(sin));
	sin.sin_family = AF_INET;
	hp = gethostbyname(HOSTNAME);
	if (hp == NULL) {
		fprintf(stderr, "chat: host '%s' unknown\n", HOSTNAME);
		exit(1);
	}
	bcopy(hp->h_addr, &sin.sin_addr, hp->h_length);
	if ((sp = getservbyname("chat", "tcp")) == NULL) {
		fprintf(stderr, "chat: chat/tcp unknown service\n");
		exit(1);
	}
	sin.sin_port = sp->s_port;
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		fprintf(stderr, "chat: can't create socket\n");
		exit(1);
	}
	if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
		fprintf(stderr, "chat: can't connect to socket\n");
		exit(1);
	}
	(void) gethostname(hostname, sizeof(hostname));
	(void) sprintf(buf, "u=%s\nh=%s\n", getlogin(), hostname);
	swrite(s, buf, strlen(buf));
	term();
}

SHAR_EOF
fi
exit 0
#	End of shell archive