UNIX Chat Program
John Temples
john at jwt.UUCP
Sat Jan 5 17:28:24 AEST 1991
This is the chat program developed for the Magpie BBS by Steve Manes.
It supports features such as multiple password-protected channels,
private messages, and logging of chat. It compiled and ran as-is on
my ESIX SVR3.2 system. The author states it should run on most
System V and Xenix systems.
#! /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 the files:
# Chat.Doc
# Makefile
# chat.c
# getline.c
# lockchat.c
# misc.c
# chat.h
# This archive created: Wed Jul 12 23:53:13 1989
export PATH; PATH=/bin:$PATH
if test -f 'Chat.Doc'
then
echo shar: will not over-write existing file "'Chat.Doc'"
else
cat << \SHAR_EOF > 'Chat.Doc'
Magpie MultiChat Manual Version 2.0
Magpie Multi-Chat v2.0
copyright (c)1989 by Steve Manes
Roxy Recorders, Inc.
648 Broadway, New York, N.Y. 10012
I N T R O D U C T I O N
-----------------------
Magpie Chat was developed as a multiuser chat program for
use with the Magpie/XENIX BBS program, however it will also
work as a stand-alone multiple-party chat. While there are
several chat programs available for Unix and Xenix, Magpie
Chat was written to provide a more complete multiuser chat
environment for dedicated chat call-in applications or for
sysops of *ix BBSes that need a secure and auditable real-
time chat program. This isn't an enhanced "write" or
"phone" utility but a full-featured, multiuser chat utility.
Page 1
Magpie MultiChat Manual Version 2.0
FEATURES
1.1 Chat Logging
Chat messages are appended to a shared data file rather than
written to the users' tty hardware. This method provides a
ready log of all chat activity readable by the local honcho
and eliminates problems with device permissions. Every 24
hours, Chat archives the current chatfile for later auditing
or for disposal and starts a new one.
1.2 Multiple Channels
There are 26 chat channels available in Chat. Channel 'A',
the default, is a public channel. Channels 'B' through 'Z'
may be password protected by the first user to log into the
channel. This will allow two or more users to enjoy a
private chat and to send channel passwords to other users
via Private chat messages.
1.3 Word Wrap
Full word-wrap of text on both input and output for users of
any terminal width (32-132 columns)is supported. By
default, every user is assumed to be on an 80-column screen
but this may be changed either on the command line or
interactively through Chat by the user. The maximum size of
a chat message is 2k, although you can increase/decrease
this by changing the value MAXCHAT in 'chat.h'.
1.4 Editor Control
The message input editor has cursor control/line editing.
The following are supported:
Ctrl-A back word Ctrl-F forward word
Ctrl-B back character Ctrl-D forward character
Ctrl-X cancel line Ctrl-R retype line
1.5 Private Messages
Users may send private messages to each other regardless of
what chat channel either is in.
Page 2
Magpie MultiChat Manual Version 2.0
1.6 Shell Access
This feature may be blocked by the local honcho if Chat is
loaded via an "intelligent" script file.
1.7 Chat Timer
By default, all chatters have unlimited time. However, a
user's time limit in any one invocation of Chat may be fixed
by the aforementioned script file.
1.8 External Pager
Summons other shell users to Chat.
1.9 Chat Lock-Out
Chat can be disabled whenever necessary by the local honcho.
Page 3
Magpie MultiChat Manual Version 2.0
DISTRIBUTION
2.1 Copyright Information
This program is a copyrighted work owned by Steve Manes and
Roxy Recorders, Inc of New York, NY. All commercial and
distribution rights are likewise reserved by Steve Manes.
You are permitted to compile and use this software free of
charge under the following conditions:
1. Copyright notices and internal prompts and strings
including the program name and author's credit are NOT
modified in any way.
2. Modifications to the source must NOT delete the
original code. Just #ifdef out the old code and add
your replacement. Also, please add any comments about
your modification and, while you're at it, your name.
You might as well take credit for the improvement.
3. That this source file is distributed with all
included files, including Chat.Doc, in the original
'shar' format.
4. That these files are distributed to others FREE OF
CHARGE. Chat source and its executable may NOT be
included in any package for which a charge of money or
exchange of services is requested or demanded.
In addition, if these files are uploaded to any system which
claims an automatic copyright claim over such uploads this
will in no way invalidate the author's original copyright or
legal ownership of his property.
Page 4
Magpie MultiChat Manual Version 2.0
INTERNALS
3.1 Chat File
Chat processes do not communicate with each other directly
but by means of a common, shared file. When a chat message
is entered, it is saved at the bottom of the current
chatfile, from which all other users are constantly polling
about once a second. Besides providing enhanced auditing
and reducing inter-process messaging, this allows chatfiles
to be archived, edited or posted to a BBS or similar
messaging system. This will also work on systems where
named pipes, another common Unix communications method, is
broken or nonexistent.
Chat automatically restarts a new chatfile at midnight.
Users who have entered Chat before midnight will be
automatically moved to the new chatfile.
Chatfiles are created as 'chatMMDD' where MM=month and
DD=day. Since Chat sets all users to the effective user
superuser has read privileges to chatfiles offline.
Login/logout information as well as the text of Private
messages between users is also included in the chatfiles.
3.2 The Chat Editor
Chat incorporates the Magpie BBS message editor line input
function, getline(). Along with instext(), this provides a
full word-wrapping editor. Partial text lines are
automatically joined by reformat() on output to chatfile.
The formatter can also be forced to insert newlines by
preceding a line of text with a '.' or a space for tabular
information. This also allows a 40-column user to read
messages entered at 80-columns with formatting for his
terminal width.
3.3 Chat Command Line Arguments
The command line contains arguments to change defaults
within Chat. They are mostly effective if Chat is called
from another program, such as a BBS script file, to force
the desired environment on selected users. By default,
everyone has unlimited Chat time, a username equal to their
LOGNAME, an 80-column screen and shell privs.
Page 5
Magpie MultiChat Manual Version 2.0
-n"name" option
By default, Chat will use the user's LOGNAME for Chat.
The -n switch overrides this.
Example: chat -n"Mr. Foo"
-tNN timer option
The -t argument will set the user's maximum allowable
time to NN minutes. If you're running a BBS with a
limited number of lines and/or an enforced user timer
this feature will be particularly welcome. Just pass
Chat the user's remaining timer on the BBS and Chat will
automatically abort when the time limit is reached.
Example: chat -t30
-cNN option
By default, all users will log into Chat with 80 column
terminal settings. The -c option will change this to
any terminal width from 32 to 132 columns, all with full
word- wrap on input and output. (Note: the chatfile is
internally formatted at 78 columns for easier offline
reading.)
-x[list] option
By default, all users have full access to the menu of
Chat options. The -x option will allow you to shut off
most commands from the user (all except except <Q>uit
and SPACEBAR). Again, on a BBS you would probably want
to prohibit users access to the <!>shell command and,
possibly, the external pager. Any commands blocked by
-x will also not appear in the user's help menu.
Example: chat -x!s
-d<path> option
Overrides the default path to the chat directory
specified in chat.h.
Example: chat -d/usr/magpie
Page 6
Magpie MultiChat Manual Version 2.0
COMPILING INFORMATION
To allow all users to read and write to the same file and to
provide general chatfile security Chat set's the user's
effective ID to 'bin'. Therefore, Chat must be compiled by
superuser, or 'root'.
Set up file defaults in "chat.h".
type 'make' and then 'make install'
Page 7
Magpie MultiChat Manual Version 2.0
BUGS
Chat is fairly vanilla but it has only been tested under
Microport Unix and SCO Xenix. If you encounter any problems
with Chat under other flavors of *nix, and particularly if
you've got a fix, I would appreciate it if you would drop me
a line at any of the below addresses (particularly Magpie-HQ
or UUCP).
If you have troubles compiling or running Chat you'll
probably find your trouble in either the 'lockchat.c' record
locking/unlocking routines or in the way your system handles
signal() interrupts. Unfortunately, the only fix I can
suggest is to find a guru for your particular flavor of *ix.
If your users are prolific chatters, keep an eye on your
Chatfile directory. A large multiuser system can accumulate
a megabyte or more of chat per day!
Enjoy -- Steve Manes
Magpie-HQ: (212)420-0527
UUCP: !{uunet|rutgers|cmcl2}!hombre!magpie!manes
SmartMail: manes at MASA.COM
Page 8
CONTENTS
FEATURES................................................. 2
1.1 Chat Logging........................................ 2
1.2 Multiple Channels................................... 2
1.3 Word Wrap........................................... 2
1.4 Editor Control...................................... 2
1.5 Private Messages.................................... 2
1.6 Shell Access........................................ 3
1.7 Chat Timer.......................................... 3
1.8 External Pager...................................... 3
1.9 Chat Lock-Out....................................... 3
DISTRIBUTION............................................. 4
2.1 Copyright Information............................... 4
INTERNALS................................................ 5
3.1 Chat File........................................... 5
3.2 The Chat Editor..................................... 5
3.3 Chat Command Line Arguments......................... 5
COMPILING INFORMATION.................................... 7
BUGS..................................................... 8
- i -
SHAR_EOF
fi # end of overwriting check
if test -f 'Makefile'
then
echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
# @(#) makefile for Magpie MultiChat
# Steve Manes, Roxy Recorders, NYC
#
# Destination directory -- should be a public directory
DESTDIR=/u/bin
# Owner and Group
OWNER=bbs
GROUP=bin
# Compiler flags
# If your compiler support the 'index' function rather than 'strchr'
# add '-DINDEX' to CFLAGS
# If your compiler doesn't support a 'void' type
# add '-DNOVOID' to CFLAGS
#
# 386 flags
CFLAGS = -O
# 286 flags
#CFLAGS = -Ms2 -O -F 4000
# this compile's for Xenix (comment out next line for SVR2)
OARG = -o chat -lx
# this one's for SVR2
# OARG = -o chat
CHATOBJ = chat.o getline.o misc.o lockchat.o
CHATSRC = chat.c getline.c misc.c lockchat.c
all: $(CHATOBJ)
cc $(CFLAGS) $(CHATOBJ) $(OARG)
# must be run by 'root'
install:
mv chat ${DESTDIR}
chown ${OWNER} ${DESTDIR}/chat
chgrp ${GROUP} ${DESTDIR}/chat
chmod ug+s ${DESTDIR}/chat
sh:
shar Chat.Doc Makefile chat.c getline.c lockchat.c misc.c chat.h > magchat.sh
rm -f magchat.sh.Z
compress magchat.sh
ln magchat.sh.Z /prog/nuucp
SHAR_EOF
fi # end of overwriting check
if test -f 'chat.c'
then
echo shar: will not over-write existing file "'chat.c'"
else
cat << \SHAR_EOF > 'chat.c'
/* @(#) chat.c -- Magpie Chat
*
* Multi-chat program for Unix
* Copyright 1988 All Rights Reserved
* Steve Manes, Roxy Recorders, Inc., 648 Broadway, New York, NY 10012
*
* Originally developed as a multiuser chat facility for
* Magpie/UNIX BBS.
*
* Args:
* -n"chat name" chatter's handle (default: system uid)
* -tnn time limit in minutes (default, no limit)
* -wnn terminal width (default: 80 columns)
* -x[list] chat commands to block from user (like !)
* -d<path> path to Chat directory
*
*/
#include "chat.h"
#include <sys/stat.h>
#include <setjmp.h>
WHOREC my;
char *prog = "\nMagpie MultiChannel MultiChat v2.1\n\
Copyright 1989 by Steve Manes\n\n-- Type ? for help --";
int mywidth = 80; /* user's default terminal width */
struct termio savterm, myterm; /* terminal structure */
int Outfid; /* chat output file handle */
FILE *Infid; /* chat input file handle */
long Chatpos; /* chat file 'last-read' position */
int Lockflag; /* used by reqlock() */
long TimesUp = -1; /* user's chat time limit */
int Today; /* today's day */
char workbuf[MAXLINE +1]; /* miscellaneous buffer */
char ignore[10]; /* command keys to ignore */
char Chatpath[MAXPATH]; /* chat file directory */
char *buff, *tail; /* message input buffer */
jmp_buf cjmp;
char *menu[] = {
"\n",
"/C columns",
"/J join chat channel",
"/L list shell users",
"/P private to user",
"/Q quit chat",
"/S shell pager",
"/T time left",
"/W who's in Chat",
"/H help",
"/! shell command",
""
};
char *use = "usage: -n\"user name\" -w[term width] -t[timer] -x[list]\n";
main(argc, argv)
char *argv[];
{
char *cp;
struct stat Stat;
strcpy(my.name, cuserid( (char *)0) ); /* set up CWHO defaults */
strcpy(my.tty, ttyname(TTYIN));
time(&my.login);
ignore[0] = '\0'; /* all commands enabled */
strcpy(Chatpath, CHATPATH); /* default Chat path */
while (--argc) { /* get command line args */
cp = *++argv;
if (*cp++ == '-') {
switch (*cp++) {
case 'n': /* chat name */
strcpy(my.name, cp);
break;
case 'w': /* user width */
mywidth = atoi(cp);
break;
case 't': /* time limit (seconds) */
TimesUp = my.login + (long)(atol(cp));
break;
case 'x': /* ignore commands */
strcpy(ignore, cp);
break;
case 'd': /* alternate chat path */
strcpy(Chatpath, cp);
break;
default: /* switch error */
puts(use);
exit(1);
}
}
}
if (stat( fpath(Chatpath, NOCHATFILE), &Stat) == SUCCESS) {
puts("\nSorry -- chat unavailable now\n");
exit(1);
}
if (mywidth < 32) mywidth = 32; /* get default term width */
if (mywidth > 132) mywidth = 132;
strupr(ignore);
/* allocate buffer space */
if (!(buff = (char *)malloc(BUFSIZE +100))) {
puts("malloc error");
exit(1);
}
if (openchat() != SUCCESS) { /* open/create chat file */
free(buff);
exit(1);
}
strupr(my.name);
setterm();
puts(prog); /* show the ad */
whozon(); /* display WHO file */
my.channel = 'A'; /* default channel: PUBLIC */
my.pswd[0] = '\0'; /* no password for channel */
if (addwho(&my) != SUCCESS) /* add name to CWHO */
quit();
announce(&my, TRUE);
Chatpos = fseek(Infid, 0L, 2); /* set read ptr to end-of-file */
chat();
quit();
}
/*----------------------------------------------------------------------
* chat()
* main routine for chat
* returns: ERROR (log out) or SUCCESS
*----------------------------------------------------------------------*/
int chat()
{
long ltime;
struct tm *TM;
static int warned;
setjmp(cjmp);
while (TRUE) {
/* if user has a timer, check time left */
if (TimesUp > -1) {
time(<ime);
if (!warned && (ltime + 120) > TimesUp) {
puts("\n** 2 minutes left **\n");
warned = TRUE;
}
if (ltime > TimesUp) {
puts("\n** Sorry, chat timer has expired **\n");
quit();
}
}
readchat(); /* display any chat waiting */
if (getcmd() == ERROR) /* get a chat command */
break;
}
}
/*----------------------------------------------------------------------
* openchat()
* composes chatfile name "CHATmm.dd" where 'mm' and 'dd' are
* today's month and day.
* returns: ERROR or SUCCESS
*----------------------------------------------------------------------*/
int openchat()
{
struct tm *TM;
char chatfile[MAXPATH +1]; /* chat filename */
long ltime;
time(<ime); /* set time now */
TM = localtime(<ime); /* get structure */
sprintf(chatfile, "%s%02d%02d", fpath(Chatpath, CHATFILE),
TM->tm_mon +1, TM->tm_mday);
/* open chat write handle */
if ((Outfid = open(chatfile, O_RDWR | O_CREAT, 0x1b0)) == ERROR) {
printf("Can't open %s for writing\n", chatfile);
return( ERROR );
}
/* open chat read handle */
if ( !(Infid = fopen(chatfile, "r"))) {
printf("Can't open %s for reading\n", chatfile);
return( ERROR );
}
Today = TM->tm_mday;
return( SUCCESS );
}
/*----------------------------------------------------------------------
* quit()
* branches here to clean up and leave program
* returns: nothing
*----------------------------------------------------------------------*/
void quit()
{
long ltime;
time(<ime);
announce(&my, FALSE);
unlock();
close(Outfid);
fclose(Infid);
ioctl(TTYIN, TCSETAW, &savterm);
killwho(&my);
free(buff);
exit(0);
}
/*----------------------------------------------------------------------
* setterm()
* sets initial terminal and signal() defaults
* returns: ERROR OR SUCCESS
*----------------------------------------------------------------------*/
int setterm()
{
ioctl(TTYIN, TCGETA, &savterm); /* save current terminal */
ioctl(TTYIN, TCGETA, &myterm); /* working terminal */
myterm.c_iflag = (IGNPAR | IGNBRK | ISTRIP | IXON | IXOFF);
myterm.c_lflag &= ~(ICANON | ECHO);
myterm.c_cc[VMIN] = 1;
myterm.c_cc[VTIME] = 0;
myterm.c_cc[VINTR] = 255; /* disable Ctrl-D for the editor */
myterm.c_cc[VQUIT] = 255; /* disable Ctrl-C .. use <Q>uit */
if (ioctl(TTYIN, TCSETAW, &myterm) == ERROR) {
puts("Can't set terminal");
return(ERROR);
}
setsigs();
return(SUCCESS);
}
/*----------------------------------------------------------------------
* setsigs()
* set default signals
* returns: nothing
*----------------------------------------------------------------------*/
void setsigs()
{
signal(SIGQUIT, quit); /* exit */
signal(SIGINT, quit);
signal(SIGHUP, quit);
signal(SIGTERM, SIG_IGN);
}
/*----------------------------------------------------------------------
* append(buff)
* write 'buff' to Outfid.
* returns: nothing
*----------------------------------------------------------------------*/
void append(buff)
char *buff;
{
char spec[3];
if (reqlock() == ERROR) /* get lock and set lseek to EOF */
return;
if (write(Outfid, buff, strlen(buff)) == ERROR)
puts("\n** File append error");
write(Outfid, "\n\n", 2); /* append two newlines */
unlock();
}
/*----------------------------------------------------------------------
* help()
* display help
* returns: nothing
*----------------------------------------------------------------------*/
void help()
{
int i;
for (i=0; *menu[i]; i++) {
if (!strchr(ignore, menu[i][1])) /* command enabled? */
printf("%s\n", menu[i]); /* yes, print it */
}
putchar('\n');
}
/*----------------------------------------------------------------------
* chattime(ltime)
* returns: pointer to formatted time string for 'ltime'
*----------------------------------------------------------------------*/
char *chattime(ltime)
long *ltime;
{
struct tm *TM;
static char tbuff[8];
int hour, min, pm;
TM = localtime(ltime);
hour = TM->tm_hour;
min = TM->tm_min;
pm = (hour > 11);
if (pm)
hour -= 12;
if (hour == 0)
hour = 12;
sprintf(tbuff, "%d:%02d%s", hour, min, pm ? "pm" : "am");
return(tbuff);
}
/*----------------------------------------------------------------------
* instext( got )
* get and insert a line of text into 'buff'
* returns: nothing
*----------------------------------------------------------------------*/
void instext( got )
char got;
{
char combuf[MAXLINE +1];
int i, eol, len, width, bufflen;
bufflen = strlen(buff);
tail = (char *)(buff + bufflen);
*tail = '\0';
width = mywidth -4;
len = 0;
strinit(combuf, MAXLINE +1);
*combuf = got;
/* get text entry */
while (TRUE) {
/* check for potential overflow */
if ((tail - buff) > (MAXCHAT - MAXLINE)) {
puts("\n**Buffer full**");
break;
}
printf("> "); /* prompt */
if (!(len = getline(combuf, width))) { /* get a line */
backspace(); /* erase the prompt */
backspace();
putchar('\n');
break;
}
eol = len;
workbuf[0] = '\0';
/* see if line fold needed */
if (len >= width && combuf[len-1] > ' ') {
for (; len && combuf[len] != ' '; len--)
;
if (!len) len = eol;
strcpy(workbuf, (char *)(combuf+len+1));
}
else for (; len && combuf[len-1] <= ' '; combuf[len--] = 0)
;
combuf[len] = '\n';
combuf[len+1] = '\0';
/* at this point, trimmed line with terminating '\n'
is in 'combuf' and any overage is in 'workbuf'.
'combuf[len]' is pointing to the terminating '\n'
*/
strcpy(tail, combuf); /* copy to buffer */
tail += (len +1);
*tail = '\0';
strinit(combuf, MAXLINE +1);
/* check for line fold on display */
if (workbuf[0]) {
for (i=strlen(workbuf); i; i--) /* erase tail */
backspace();
strcpy(combuf, workbuf);
}
putchar('\n');
}
if ((tail-buff) == bufflen) /* no text */
return;
*(tail -1) = '\0'; /* remove trailing newline */
reformat(buff, 78); /* reformat buffer */
append(buff, FALSE); /* write to chatfile */
}
/*----------------------------------------------------------------------
* getmsg()
* grabs a single formatted chat message to 'buff'
* returns: -1 = nothing to read
* 0 = message not readable by user
* 1 = readable message waiting
*----------------------------------------------------------------------*/
int getmsg()
{
char *p;
int gotesc = FALSE;
int rc = FALSE;
buff[0] = '\0';
Chatpos = ftell(Infid); /* note file position */
while (TRUE) {
if ( fgets(workbuf, MAXLINE, Infid) == NULL) {
if (gotesc == FALSE)
rc = ERROR;
break;
}
p = workbuf;
if (*p == ESC) { /* beginning of message? */
if (gotesc) {
fseek(Infid, Chatpos, 0); /* reset */
break;
}
gotesc = TRUE;
p++; /* offset to message type */
if (*p == my.channel || *p == ALL) {
rc = TRUE;
p += 2;
}
else if (*p == PRIVATE &&
!strncmp((p+1), my.name, strlen(my.name))) {
rc = TRUE;
p += strlen(my.name) +1;
}
}
if (rc == TRUE)
strcat(buff, p); /* copy to buffer */
Chatpos = ftell(Infid);
}
if (rc == TRUE)
reformat(buff, mywidth -1); /* format for terminal */
return( rc );
}
/*----------------------------------------------------------------------
* readchat()
* reads any text present into 'buff' and displays
* returns: nothing
*----------------------------------------------------------------------*/
void readchat()
{
long ltime;
int rc;
char *p;
struct tm *TM;
while ((rc = getmsg()) != ERROR) {
if (rc == TRUE) {
for (p= buff; *p; p++)
putchar(*p);
}
}
/* Tricky here... check the date to make sure the day hasn't
* rolled over. If so, folks logging on after midnight will be
* reading/writing to another chatfile. If date rollover,
* switch to new chat file.
*/
time(<ime); /* check the day */
TM = localtime(<ime); /* time to switch chatfile? */
if (TM->tm_mday != Today) { /* start new chat file */
puts("\n-- Day rollover: Switching to new chat file");
puts("-- Some pending messages may be lost");
close(Outfid);
fclose(Infid);
if (openchat() != SUCCESS) {
puts("\n** Internal error **");
quit();
}
announce(&my, TRUE);
Chatpos = fseek(Infid, 0L, 2); /* set end-of-file */
}
}
/*----------------------------------------------------------------------
* getcmd()
* gets a command key
* waits 1 second for input, then breaks
* else, checks for valid command.
* returns: ERROR (quit program) or SUCCESS
*----------------------------------------------------------------------*/
int getcmd()
{
char c, *user;
long ltime;
signal(SIGALRM, gotit); /* set up timeout */
alarm(1);
c = getchar() & 0x7f; /* grab 7-bit character */
alarm(0);
if (c == '/') {
putchar('/');
c = toupper(getchar() & 0x7F); /* get command */
if (c == 'Q' || c == '/') {
puts("Quit\n");
return(ERROR);
}
if (strchr(ignore, c)) { /* blocked command? */
puts("\nInvalid command\n");
return(SUCCESS); /* yes, ignore it */
}
/* do command */
switch (c) {
case 'J': /* change chat channel */
puts("Join channel");
channel(&my);
break;
case 'C': /* set terminal width */
puts("Columns");
getwidth();
break;
case 'W': /* users in chat */
puts("Who's On");
whozon();
break;
case 'P': /* User->user private chat message */
puts("Private");
if (!(user = finduser()))
break;
sprintf(buff, "\033%c%s%c**Private from %s:\n",
PRIVATE, user, 1, my.name);
instext(0);
break;
case '?': /* help */
case 'H':
puts("Help");
help();
break;
case 'T': /* timer */
puts("Time");
time(<ime);
printf("\nTime now: %s\n", chattime(<ime));
printf("Time on: %ld:%02ld\n", (ltime-my.login) /60,
(ltime-my.login) %60);
if (TimesUp > -1)
printf("Time left: %ld:%02ld\n",
(TimesUp-ltime) /60, (TimesUp-ltime) %60);
break;
case 'L': /* list logged-in users */
puts("List shell users\n");
shpager(FALSE);
break;
case 'S': /* page logged in user via /dev/? */
puts("Shell pager\n");
shpager(TRUE);
break;
case '!':
strinit(workbuf, 81);
printf("sh command: ");
if (getline(workbuf, 80)) {
putchar('\n');
if (chexec(workbuf) == 127)
puts("\nsh error");
else {
printf("\nHit any key. . .");
getchar();
}
}
putchar('\n');
break;
case '\b':
backspace();
break;
default:
puts("(Invalid command)");
break;
}
return(SUCCESS);
}
else if (c == '?')
help();
/* get a line of chat text */
else if (isalnum(c)) {
sprintf(buff, "\033%c %s: ", my.channel, my.name);
instext(c);
}
else if (isspace(c)) {
sprintf(buff, "\033%c %s: ", my.channel, my.name);
instext(0);
}
return( SUCCESS );
}
/*----------------------------------------------------------------------
* gotit()
* branches here on timeout above
* returns: nothing
*----------------------------------------------------------------------*/
void gotit()
{
alarm(0);
longjmp(cjmp, 0);
}
/*----------------------------------------------------------------------
* chexec(arg)
* execute a shell command
* returns: execl status
*----------------------------------------------------------------------*/
int chexec(arg)
char *arg;
{
int pid, rc, wrc;
if ((pid = fork()) == 0) {
setuid(getuid());
execl("/bin/sh", "sh", "-c", arg, 0);
_exit(127);
}
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGHUP, SIG_IGN);
while ((wrc = wait(&rc)) != pid && wrc != -1)
;
setsigs();
return(rc);
}
SHAR_EOF
fi # end of overwriting check
if test -f 'getline.c'
then
echo shar: will not over-write existing file "'getline.c'"
else
cat << \SHAR_EOF > 'getline.c'
#include <stdio.h>
#include <ctype.h>
#define EOL 0
#define FALSE 0
#define TRUE 1
#define BEL 7
#define BS '\b'
#define TAB 9
#define CR 13
#define LF 10
#define ESC 27
#define SPACE ' '
#define DEL 127
#define TABLEN 8
#define ONESEC 18L
void backspace();
/*--------------------------------------------------------------------------
* getline( buff, maxlen )
*
* buff: input buffer
* maxlen: maximum characters
*-------------------------------------------------------------------------*/
int getline(buff, maxlen)
char buff[];
int maxlen;
{
int curs, i;
char c;
if (*buff)
printf("%s", buff); /* display anything in buffer */
curs = strlen(buff); /* and advance 'curs' */
while (curs <= maxlen) {
c = getchar() & 0x7f;
if (c >= SPACE && c != DEL) { /* printable character */
buff[curs++] = c; /* overwrite/insert character */
putchar(c);
continue;
}
switch (c) { /* special character received */
case CR: /* done */
return(strlen(buff)); /* return buffer length */
case 'H'-64: /* backspace */
case DEL: /* or delete */
if (!curs)
break; /* beginning of line... ignore */
if (!buff[curs]) { /* backspace at end of line */
backspace(); /* display destructive BS */
buff[--curs] = '\0'; /* zap the buffer character */
break;
}
putchar(BS); /* cursor left */
curs--; /*.. then fall through to next
'case'.. */
case 'G'-64: /* delete in-line */
if (buff[curs]) { /* inside a line? */
strcpy(buff, buff+1); /* shift buffer left */
for (i=curs; buff[i]; i++) /* display it */
putchar(buff[i]);
putchar(SPACE); /* zap trailing char */
for (; i >= curs; i--) /* return cursor */
putchar(BS);
}
break;
case 'D'-64: /* cursor right */
if (buff[curs]) /* if not at right column... */
putchar(buff[curs++]); /* move to character-right */
break;
case 'B'-64: /* cursor left */
if (curs) { /* if not at left column... */
putchar(BS); /* cursor left */
curs--; /* mark postion */
}
break;
case 'A'-64: /* word left */
if (curs) { /* if not at left column... */
do /* move left through any */
putchar(BS); /* whitespace */
while (curs && buff[--curs] <= SPACE)
;
while (curs && buff[curs-1] > SPACE) {
putchar(BS); /* move left through text */
curs--;
}
}
break;
case 'F'-64: /* word right */
if (buff[curs]) { /* if not at right column... */
while (buff[curs] > SPACE)
putchar(buff[curs++]); /* show char-right */
while (buff[curs] == SPACE) /* move through SPACEs */
putchar(buff[curs++]);
}
break;
case 'X'-64: /* delete line */
while (buff[curs]) { /* delete characters-right */
putchar(SPACE);
curs++;
}
while (curs) { /* delete to beginning of line */
backspace();
buff[curs--] = '\0';
}
curs = 0;
buff[curs] = '\0';
break;
case 'R'-64: /* retype line */
printf("\n> ");
for (curs=0; buff[curs]; curs++) /* retype the buffer */
putchar(buff[curs]);
break;
case TAB: /* expand tab to SPACEs */
while (curs < maxlen) { /* don't expand beyond maxlen */
buff[curs++] = SPACE; /* insert a space */
putchar(SPACE); /* show it */
if ( !(curs % TABLEN) ) /* and break if at tabstop */
break;
}
break;
}
}
return (strlen(buff));
}
/*----------------------------------------------------------------------
* backspace()
* prints destructive backspace
* returns: nothing
*----------------------------------------------------------------------*/
void backspace()
{
putchar('\b');
putchar(' ');
putchar('\b');
}
SHAR_EOF
fi # end of overwriting check
if test -f 'lockchat.c'
then
echo shar: will not over-write existing file "'lockchat.c'"
else
cat << \SHAR_EOF > 'lockchat.c'
/* lockchat.c -- record locking functions for Magpie chat */
#include "chat.h"
#include <sys/locking.h>
#define MAXTRY 10
/*----------------------------------------------------------------------
* lockf(fid, func, size)
*
* Replacement for SVR2 lockf(), which doesn't work correctly
* in some versions of Unix and in Xenix.
*----------------------------------------------------------------------*/
int lockf(fid, func, size)
int fid, func;
long size;
{
switch (func) {
case F_ULOCK: return( locking(fid, LK_UNLCK, size) );
case F_LOCK: return( locking(fid, LK_LOCK, size) );
case F_TLOCK: return( locking(fid, LK_NBLCK, size) );
default: return(ERROR);
}
}
/*----------------------------------------------------------------------
* reqlock()
* locks chat file for end-of-file write
* returns: ERROR or SUCCESS
*----------------------------------------------------------------------*/
int reqlock()
{
long fpos;
int tries;
fpos = lseek(Outfid, 0L, 2); /* mark current file pos */
lseek(Outfid, 0L, 0); /* move to BOF and lock */
signal(SIGHUP, unlock); /* if hang up, assure unlock */
Lockflag = TRUE;
tries = 0;
while (lockf(Outfid, F_TLOCK, 0L) == ERROR) {
if (tries++ == MAXTRY) {
puts("Can't acquire file lock");
Lockflag = FALSE;
break;
}
sleep(2);
puts("Awaiting lock");
}
lseek(Outfid, fpos, 0); /* restore file pos */
setsigs();
return( (Lockflag == TRUE) ? SUCCESS : ERROR);
}
/*----------------------------------------------------------------------
* unlock()
* unlock chatfile
* returns: nothing
*----------------------------------------------------------------------*/
void unlock()
{
long fpos;
if (Lockflag == TRUE) {
fpos = lseek(Outfid, 0L, 2); /* save current file pos */
lseek(Outfid, 0L, 0); /* set to BOF for unlock */
signal(SIGHUP, SIG_IGN);
lockf(Outfid, F_ULOCK, 0L);
lseek(Outfid, fpos, 0); /* restore file pos */
Lockflag = FALSE;
}
setsigs();
}
SHAR_EOF
fi # end of overwriting check
if test -f 'misc.c'
then
echo shar: will not over-write existing file "'misc.c'"
else
cat << \SHAR_EOF > 'misc.c'
/* misc.c -- miscellaneous routines for Magpie 'chat' */
#include "chat.h"
#include <utmp.h>
static int WhoFid = 0;
/*----------------------------------------------------------------------
* finduser()
* asks for username and checks WHO for match
* returns: username or NULL
*----------------------------------------------------------------------*/
char *finduser()
{
int fid;
WHOREC who;
printf("\nPrivate message to: "); /* get username */
strinit(workbuf, MAXNAME +1); /* for getline() */
if (!getline(workbuf, MAXNAME)) {
putchar('\n');
return( NULL );
}
putchar('\n');
strupr(workbuf); /* name -> uppercase */
if ((fid = open( fpath(Chatpath, WHOFILE), O_RDONLY)) == ERROR)
return( (char *)0 );
while (read(fid, &who, sizeof(WHOREC)) == sizeof(WHOREC)) {
if (!strcmp(who.name, workbuf))
return(workbuf);
}
puts("\nUser not in Chat");
return( NULL );
}
/*----------------------------------------------------------------------
* strupr(s)
* converts string to uppercase
* returns: pointer to string
*----------------------------------------------------------------------*/
char *strupr(s)
char *s;
{
char *p;
for (p=s; *p; p++)
*p = toupper(*p);
return( s );
}
/*----------------------------------------------------------------------
* strinit(s, len)
* null out a string, s', to length, 'len'.
* returns: nothing
*----------------------------------------------------------------------*/
void strinit(s, len)
char *s;
int len;
{
memset(s, '\0', len);
}
/*----------------------------------------------------------------------
* getwidth()
* sets user's terminal width
* returns: nothing
*----------------------------------------------------------------------*/
void getwidth()
{
int n;
printf("\nNew terminal width: ");
strinit(workbuf, 5);
if (getline(workbuf, 4)) {
n = atoi(workbuf);
if (n >= 32 && n <= 132)
mywidth = n;
}
putchar('\n');
}
/*----------------------------------------------------------------------
* reformat(buff, winsize)
* reformats 'buff' for user's window width
* returns: nothing
*----------------------------------------------------------------------*/
void reformat(buff, width)
char *buff;
int width;
{
extern char *tail;
char *c, *lastbrk, *lastspace;
c = lastspace = lastbrk = buff;
while (*c == '\n') /* preserve leading newlines */
c++;
while (*c) {
if (*c == '\n') { /* check for newline */
if (*(c+1)=='\n' || *(c+1)=='.' || *(c-1)=='\n'||
*(c+1)==' ') {
lastbrk = c++;
continue;
}
*c = ' ';
}
if (*c == ' ') /* check for line break */
lastspace = c;
/* check for wrap... */
/* if no break space, chop up line */
if (c >= (lastbrk + width -1)) {
if (lastspace <= lastbrk)
lastspace = c;
*lastspace = '\n'; /* add a newline */
lastbrk = ++lastspace;
c++;
continue;
}
c++;
}
tail = c; /* reset end-of-buffer addr */
}
/*----------------------------------------------------------------------
* getwhoent()
* returns next entry in MagChat 'cwho' file
* returns: WHOREC *
*----------------------------------------------------------------------*/
WHOREC *getwhoent()
{
static WHOREC who;
if (WhoFid == 0) { /* open if not */
if ((WhoFid = open( fpath(Chatpath, WHOFILE), O_RDWR)) == ERROR) {
WhoFid = 0;
return( (WHOREC *) 0 );
}
}
if (read(WhoFid, &who, sizeof(WHOREC)) == sizeof(WHOREC))
return( &who );
endwhoent();
return( (WHOREC *) 0 );
}
/*----------------------------------------------------------------------
* endwhoent()
* close WHO file
* returns: nothing
*----------------------------------------------------------------------*/
void endwhoent()
{
if (WhoFid > 0) {
close(WhoFid); /* end of file */
WhoFid = 0; /* close and exit */
}
}
/*----------------------------------------------------------------------
* addwho(rec)
* add CWHO record
* file is self-maintaining
* returns: ERROR or SUCCESS
*----------------------------------------------------------------------*/
int addwho(rec)
WHOREC *rec;
{
WHOREC who;
long offset;
int fid, rc;
if ((fid = open( fpath(Chatpath, WHOFILE), O_RDWR | O_CREAT,
0x1b4)) == ERROR) {
perror(workbuf);
return( ERROR );
}
/* find a deleted WHOREC slot and re-use it, else append new record */
offset = 0;
while (read(fid, &who, sizeof(WHOREC)) == sizeof(WHOREC)) {
if (who.name[0] == 0)
break;
offset += (long)sizeof(WHOREC);
}
lseek(fid, offset, 0);
rc = write(fid, rec, sizeof(WHOREC));
close(fid);
if (rc == ERROR) {
printf("\nError adding name to %s", WHOFILE);
return( ERROR );
}
return( SUCCESS );
}
/*----------------------------------------------------------------------
* killwho(rec)
* remove CWHO record
* returns: nothing
*----------------------------------------------------------------------*/
void killwho(rec)
WHOREC *rec;
{
WHOREC *who;
long offset = 0;
endwhoent(); /* assure Whofile closed */
while ((who = getwhoent()) > 0) {
if (who->name[0] && /* active record? */
!strcmp(who->tty, rec->tty)) { /* our tty? */
strinit(who, sizeof(WHOREC)); /* null it out */
lseek(WhoFid, offset, 0); /* rewind top of rec */
write(WhoFid, who, sizeof(WHOREC)); /* rewrite */
break;
}
offset += (long)sizeof(WHOREC);
}
endwhoent();
}
/*----------------------------------------------------------------------
* whozon()
* display CWHO file
* returns: nothing
*----------------------------------------------------------------------*/
void whozon()
{
int users = 0;
WHOREC *who;
putchar('\n');
while ((who = getwhoent()) > 0) {
if (who->name[0]) {
printf("Channel %c: %s on %s at %s\n", who->channel,
who->name, who->tty, chattime(&who->login));
users++;
}
}
if (!users)
puts("[Nobody in Chat]");
putchar('\n');
}
/*----------------------------------------------------------------------
* channel(rec)
* change Chat channel
* B-Z channels may be secured private with a pswd if not already
* secured. If secured, ask for a pswd. If not, allow one to be
* set.
* returns: nothing
*----------------------------------------------------------------------*/
void channel(rec)
WHOREC *rec;
{
char newchan;
WHOREC *who;
int occupied = 0;
printf("\nYou are currently on Channel %c", my.channel);
printf("\nEnter new Chat channel (A-Z) or RETURN to abort: ");
newchan = getchar() & 0x7f;
newchan = toupper(newchan);
printf("%c\n", newchan);
if (newchan == '\r' || newchan == my.channel)
return;
if ( !isalpha(newchan) ) {
printf("\nInvalid channel '%c'\n", newchan);
return;
}
if (newchan == 'A') { /* join public channel */
join(rec, newchan, FALSE); /* with no password */
return;
}
endwhoent(); /* assure Whofile closed */
/* check to see if we're the first to use this channel */
while ((who = getwhoent()) > 0) {
if (who->channel == newchan) { /* someone here */
occupied++;
if (strlen(who->pswd) > 0) { /* and it's got a pwd */
printf("\nChannel %c requires password\n",
who->channel);
printf("Enter password: ");
strinit(workbuf, MAXPWD +1);
if (getline(workbuf, MAXPWD) &&
!strcmp(workbuf, who->pswd)) {
endwhoent();
join(rec, newchan, FALSE);
return;
}
else { /* password failed */
puts("\nSorry!");
endutent();
return;
}
}
}
}
endutent();
/* if channel unoccupied, join it as emperor */
if (!occupied)
join(rec, newchan, TRUE);
}
/*----------------------------------------------------------------------
* join(rec, chan, getpswd)
* Switch to new Chat channel 'chan'.
* If 'getpswd', allow user to set one for channel
* returns: FALSE
*----------------------------------------------------------------------*/
void join(rec, chan, getpswd)
WHOREC *rec;
char chan;
int getpswd;
{
WHOREC *who;
long offset = 0;
printf("\n\nJoining Channel %c\n", chan);
/* find me in the Whofile and change to new channel */
while ((who = getwhoent()) > 0) {
if (who->name[0] && !strcmp(who->tty, rec->tty)) {
if (getpswd) {
puts("\nYou may set a password for this channel");
printf("Enter password or RETURN for none: ");
strinit(workbuf, MAXPWD +1);
if (getline(workbuf, MAXPWD))
strcpy(who->pswd, workbuf);
else who->pswd[0] = '\0';
putchar('\n');
}
who->channel = chan;
lseek(WhoFid, offset, 0); /* rewind top of rec */
write(WhoFid, who, sizeof(WHOREC)); /* rewrite */
announce(rec, FALSE); /* logout */
rec->channel = chan;
announce(rec, TRUE); /* login */
break;
}
offset += (long)sizeof(WHOREC);
}
endwhoent();
}
/*----------------------------------------------------------------------
* announce(rec, login)
* announce login or logout to chat channel
* returns: nothing
*----------------------------------------------------------------------*/
void announce(rec, login)
WHOREC *rec; /* user record */
int login; /* entering chat channel */
{
sprintf(workbuf, "\033%c %s ==> %s at %s", rec->channel,
login ? "In" : "Out", rec->name, chattime(&rec->login));
append(workbuf, TRUE);
}
/*----------------------------------------------------------------------
* shpager(call)
* shows system on-line
* if 'call', send chat summons
* returns: nothing
*----------------------------------------------------------------------*/
void shpager(call)
int call;
{
struct utmp UT;
char dev[20][12]; /* device address */
int fid, i=0, n;
FILE *fp;
if ((fid = open(UTMPFILE, O_RDONLY)) == ERROR) {
printf("\nCan't open %s", UTMPFILE);
return;
}
while (i < 20 && read(fid, &UT, sizeof(UT)) == sizeof(UT)) {
if (UT.ut_line[0] && UT.ut_name[0] &&
strncmp(UT.ut_name, "LOGIN", 5)) {
strcpy(dev[i], UT.ut_line); /* copy device */
printf("#%-4d%-8s %-12s %s\n", i, UT.ut_name, UT.ut_line,
chattime(&UT.ut_time));
i++;
}
}
putchar('\n');
close(fid);
if (!call) /* send chat request? */
return; /* nope, exit */
/* send a message via /dev */
strinit(workbuf, 5);
printf("\nSend chat page to # ");
if (!getline(workbuf, 4)) { /* get user */
putchar('\n');
return;
}
if ((n = atoi(workbuf)) > i) /* out of range */
return;
sprintf(workbuf, "/dev/%s", dev[n]);
if (!(fp = fopen(workbuf, "w"))) {
puts("\nCan't open device");
return;
}
fprintf(fp, "\07\n\n** Chat Request from %s on %s", my.name, my.tty);
fprintf(fp, "\n** From shell: type 'chat'");
fprintf(fp, "\n** From Magpie: type 'OC'\n\07");
fclose(fp);
puts("\nSummons sent");
}
/*----------------------------------------------------------------------
* fpath(path, name)
* returns a full filename for path/name
*----------------------------------------------------------------------*/
char *fpath(path, name)
char *path, *name;
{
static char myfile[MAXPATH + MAXNAME +1];
if (path[ strlen(path) -1 ] != '/')
sprintf(myfile, "%s/%s", path, name);
else sprintf(myfile, "%s%s", path, name);
return( myfile );
}
SHAR_EOF
fi # end of overwriting check
if test -f 'chat.h'
then
echo shar: will not over-write existing file "'chat.h'"
else
cat << \SHAR_EOF > 'chat.h'
/* chat.h */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <ctype.h>
#include <termio.h>
#include <errno.h>
#undef toupper /* use the function version */
#define XENIX /* this one's for Xenix */
/* path to chatfile (overridable with argument -d) */
#define CHATPATH "/u/local/chatfiles"
/* file prefix for chat text file */
#define CHATFILE "chat"
/* filename for chat user file */
#define WHOFILE "CWHO"
/* filename for 'chat-closed' file */
#define NOCHATFILE "Closed"
#define UTMPFILE "/etc/utmp"
#define MAXPATH 60 /* maximum path length */
#define MAXNAME 25 /* maximum username length */
#define MAXTTYNAME 15 /* maximum ttyname string length */
#define MAXCHAT 2000 /* buffer size for chat message */
#define MAXPWD 8 /* password length */
#define BUFSIZE (MAXCHAT *2) /* maximum output buffer size */
#define MAXLINE 132 /* maximum chat line length */
#define TRUE 1
#define FALSE 0
#define SUCCESS 0
#define ERROR -1
#define TTYIN 1 /* terminal handle */
#define ESC '\033'
/*----------------------------------------------------------------------
* Special chat message types
*----------------------------------------------------------------------*/
#define ALL '0' /* message to ALL */
#define PRIVATE '1' /* private message */
/*----------------------------------------------------------------------
* WHO structure
*----------------------------------------------------------------------*/
typedef struct {
char name[MAXNAME +1]; /* user's name */
char tty[MAXTTYNAME +1]; /* device name */
long login; /* login time */
char channel; /* chat channel */
char pswd[MAXPWD +1]; /* password for channel */
} WHOREC;
/*----------------------------------------------------------------------
* strchr/index and 'void' dependencies
*----------------------------------------------------------------------*/
#ifdef INDEX
#define strchr index
#endif
#ifdef NOVOID
#define void int
#endif
/*----------------------------------------------------------------------
* extern
*----------------------------------------------------------------------*/
char *ttyname(), *getlogin(), *chattime(), *strupr(), *finduser(),
*fpath();
long atol();
void quit(), unlock(), setsigs(), append(), timeout(), help(),
whozon(), killwho(), instext(), gotit(), strinit(), signin(),
endwhoent(), channel(), join(), announce(), readchat();
WHOREC *getwhoent();
extern char workbuf[], *buff, *tail, Chatpath[];
extern int mywidth, Outfid, Lockflag, errno;
extern long TimesUp, Chatpos;
extern WHOREC my;
SHAR_EOF
fi # end of overwriting check
# End of shell archive
exit 0
--
John W. Temples -- john at jwt.UUCP (uunet!jwt!john)
More information about the Alt.sources
mailing list