/* * $Id: party.c,v 1.6 2002/08/23 13:38:15 howardjp Exp $ * * Copyright (c) 1990 * Jan Wolter. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Jan Wolter * and his contributors. * 4. Neither the name of Jan Wolter nor the names of his contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY JAN WOLTER AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL JAN WOLTER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The party program simulates a party line, where any number of users * can talk at the same time. It was originally developed under System * III Unix, and as such communicates through a log file rather than * using interprocess communications. It can only be used between * different computers if they can share the log file via some sort * of NFS-like mechanism. It should however work under most any version * of Unix. * * This program may be freely distributed without permission of the * author. No permission is needed to run this software anywhere. * * Dr. Jan Wolter * janc@cybernet.org * janc@izzy.net * * RELEASE HISTORY (version numbers before 2.4 were assigned retroactively) * * version 1.0: Original two-process party program by Marcus Watts. * version 1.8: Jan Wolter's bug fixes, changes in output format, added * partytab * version 2.0: Single process rewrite by Jan Wolter. * version 2.1: Added user settable options. * version 2.2: Added multiple channels, external filter control. * version 2.3: Major internal clean-up, reorganized output filters, special * commands added, noises added. * version 2.4: Added "who" command, BSD compatibility. The Grex release. * version 2.5: Added volatile channels. :name command. * version 2.6: Minor bug fixes, System V and SCO portability patches. * version 2.7: Closable channels, BSDI bug fixes, other minor bug fixes. * version 2.8: Login names more than 8 characters for next BSDI version; * norepeat option added; sorted :who listings; showevent. * version 2.9: Idle time outs, capacity limits, intro commands, no control * characters in shelled command name * version 2.10: Add "raw" option and :ignore and :notice commands. * version 2.10a: SIGTERM makes a clean exit. Fixed Marcus's SIGPIPE bug. * :list in three columns. Added "uniquename". Better ttyname * checking. Use MAIL environment variable. Fixed /dev/tty * cloaking bug. :ignore tracks name changes. * version 2.12: Minor fixes for FreeBSD - howardjp */ #include "party.h" #include "opt.h" #include #include #include #include #ifdef VAR_ARGS #include #endif /*VAR_ARGS*/ char *version= "2.12"; /* current party version */ int inhelp=0; /* Are we printing help text? */ long tailed_from=0L; /* File offset we last tailed back from */ FILE *wfd; /* Party file open for write */ int rst; /* Party file open for read */ char *channel= NULL; /* Current channel name (NULL is outside)*/ char *progname; /* Program name */ char mailfile[80]; /* Name of mail file */ int mailfuse; /* counter for deciding when to check mail */ long mailsize; /* old size of mail file */ int real_id, eff_id; /* My real and effective group or user id */ char inbuf[BFSZ+INDENT+2]; /* Text buffer - first 10 for "name: " */ char *txbuf= inbuf + INDENT; /* Text buffer - pointer to respose portion */ /* of inbuf */ FILE *debug= NULL; /* Debug file, if one is open */ vint (*oldsigpipe)(); /* Old SIGPIPE handler */ jmp_buf jenv; main(argc,argv) int argc; char **argv; { register int n; char ch, *pnl; long lastaction; progname= leafname(argv[0]); /* Get the program name */ /* Look self up in utmp */ if (finduser(logtty,logname,&logtime)) exit(1); setrealname(); /* Get the user name */ initopts(); /* Set default options */ readtab(); /* Read options from partytab */ if (who_open()) exit(1); /* Open the partytmp file */ /* Check if we are just doing a pwho command */ if (opt[OPT_USERLIST].yes) { who_list(stdout,'c'); exit(0); } /* Check if there is room for one more in party - race conditions * could allow the capacity to be exceeded if two people enter at * once, but who cares? */ if (opt[OPT_CAPACITY].yes && who_count() >= atoi(opt[OPT_CAPACITY].str)) { printexec(stderr,opt[OPT_FULLMESG].str); exit(1); } /* If possible, set OPT_COLS from TERMCAP at this point */ setcols(); /* Read options from PARTYOPTS env var */ if (opt[OPT_ENV].yes && (pnl= getenv("PARTYOPTS"))) parseopts(pnl,0); /* Read options from parameters */ if (opt[OPT_ARG].yes) for (n= 1; n < argc; n++) parseopts(argv[n],2); #ifdef SUID real_id= getuid(); eff_id= geteuid(); #endif #ifdef SGID real_id= getgid(); eff_id= getegid(); #endif initmodes(); setmailfile(); initoutput(); if (who_enter()) done(1); printexec(stderr,opt[OPT_INTRO].str); /* Put up the signal handlers */ signal(SIGHUP,(void (*)())hup); signal(SIGQUIT,(void (*)())intr); signal(SIGINT,(void (*)())intr); signal(SIGALRM,(void (*)())alrm); signal(SIGTERM,(void (*)())term); oldsigpipe= signal(SIGPIPE,SIG_IGN); #ifdef BSD signal(SIGTSTP,(void (*)())susp); #endif /*BSD*/ #ifdef WINDOWS signal(SIGWINCH,(void (*)())setcols); #endif /*WINDOWS*/ if (join_party(opt[OPT_START].str)) exit(1); /* Get in cbreak/noecho mode */ STTY(0,&cbreak); /* Main loop */ lastaction= time((long *)0); for(;;) { /* Copy text while I can -- otherwise go to input mode */ /* Someday I need to think about a select() call in here */ if (output()) { /* Wait four seconds for a command */ alarm(4); if (!setjmp(jenv)) { read(0,&ch,1); /* Alarm busts us out of here */ alarm(0); docmd(ch); lastaction= time((long *)0); } /* Check for new mail or idleness */ if (mailfuse-- == 0) { struct stat mailstat; /* Check if he has been idle a long time */ if (opt[OPT_IDLEOUT].yes && time((long *)0) - lastaction > atoi(opt[OPT_IDLEOUT].str)*60) { fprintf(stderr, "You have been idle in party for more than %s minutes\nBye!\n", opt[OPT_IDLEOUT].str); done(0); } /* Check if size of mailfile has increased */ mailfuse= MAILDELAY; if (stat(mailfile,&mailstat)) mailsize= 0L; else { if (mailstat.st_size > mailsize) fprintf(stderr,"You have more mail\n"); mailsize= mailstat.st_size; } } } } } alrm() { signal(SIGALRM,(void (*)())alrm); longjmp(jenv,1); } /* HELP: Prints a file name to the screen. An interrupt brings you back to * party. If complain is false, it silently does nothing when the file * doesn't exist. */ help(filename,complain) char *filename; int complain; { register int hf; register int n; if ((hf= open(filename,0)) < 0) { if (complain) err("Cannot open file %s\n",filename); } else { if (!setjmp(jenv)) { inhelp= 1; while((n= read(hf,txbuf,BFSZ)) > 0) write(1,txbuf,n); } close(hf); inhelp= 0; } } /* READFILE: This reads a file into the partylog, much as if you had typed it * in, except that each line is prepended with a space instead of your logname. * Files must be readable *both* to party and to the user. */ readfile(filename) char *filename; { FILE *fp; int readlim= convert(opt[OPT_READLIM].str); int lines= 0; if (readlim == 0) { err("File reading not enabled in this channel\n"); return; } /* Can only open files if user and party can read it */ be_user(); if (access(filename,4) || (fp= fopen(filename,"r")) == NULL) { err("Cannot open input file %s\n",filename); be_party(); return; } be_party(); /* Print user's name */ txbuf[0]= '\n'; txbuf[1]= '\0'; LOCK(wfd); fseek(wfd,0L,2); fputs(inbuf,wfd); fflush(wfd); txbuf[0]= ' '; while ((lines++ < readlim) && fgets(txbuf+1,BFSZ-1,fp)) { fseek(wfd,0L,2); fputs(txbuf,wfd); if (strchr(txbuf,'\n') == NULL) fputc('\n',wfd); fflush(wfd); } UNLOCK(wfd); fclose(fp); } /* CHN_FILE_NAME: return the channel file name for the current channel. * Somebody needs to free this. */ char *chn_file_name(chn,keeplog) char *chn; int keeplog; { char *file; file= (char *)malloc((mtype)(strlen(opt[OPT_DIR].str)+CHN_LEN+6)); sprintf(file,"%s/%s.%s", opt[OPT_DIR].str, chn, keeplog?"log":"tmp"); return(file); } /* JOIN_PARTY: changes from the current channel to the new channel. Either one * of those could be NULL. Returns 1 if the named channel doesn't exist. */ join_party(nch) char *nch; { char *file; FILE *tmp_wfd; int tmp_rst,oumask,waskept; long now= time((long *)0); char newchannel[CHN_LEN+1]; static char ch[CHN_LEN+1]; if (debug) db("join_party %s\n",nch?nch:"(nil)"); waskept= opt[OPT_KEEPLOG].yes; /* If we are to enter a new channel, make sure we can */ if (nch != NULL) { /* Make a local copy of name */ strncpy(newchannel,nch,CHN_LEN); newchannel[CHN_LEN]= '\0'; if (badname(newchannel)) { err("improper channel name\n"); return(1); } #ifndef NOCLOSE if (!enter_closed(newchannel)) return(1); #endif /*NOCLOSE*/ /* Set the channel options */ if (chnopt(newchannel)) { err("channel %s does not exist\n",newchannel); return(1); } if (debug) printopts(debug,0,' ',"all"); /* Construct the channel log file name */ file= chn_file_name(newchannel,opt[OPT_KEEPLOG].yes); /* Open the partyfile to read - if it doesn't exist create it */ if ((!opt[OPT_MAYCLOSE].yes && access(file,4)) || (tmp_rst= open(file,O_RDONLY)) < 0) { /* I don't have read access, or the file doesn't exist */ oumask= umask(000); if (!access(file,0) || (tmp_rst= open(file,O_RDONLY|O_CREAT, opt[OPT_MAYCLOSE].yes ? DEP_MODE : CHN_MODE)) < 0) { err("channel %s not accessible\n(%s unreadable)\n", newchannel,file); free(file); umask(oumask); if (channel != NULL) chnopt(channel); return(1); } umask(oumask); } /* Open partyfile to write */ if ((tmp_wfd= fopen(file,"a")) == NULL) { err("Cannot write partyfile %s\n",file); close(tmp_rst); free(file); if (channel != NULL) chnopt(channel); return(1); } free(file); if (debug) db("join_party: new channel open\n"); } /* If we were in some channel, get out */ if (channel != NULL) { if (nch == NULL) sprintf(txbuf,"---- %s leaving (%.12s)\n", name,ctime(&now)+4); else sprintf(txbuf, "---- %s switching to channel %s (%.12s)\n", name,newchannel,ctime(&now)+4); write(out_fd,txbuf,strlen(txbuf)); /* Put departure message in the file */ append(txbuf,wfd); /* Close the old files */ fclose(wfd); close(rst); /* If it was a temporary channel and it is empty, delete it */ if (!waskept && who_empty(channel)) { file= chn_file_name(channel,0); unlink(file); free(file); #ifndef NOCLOSE file= usr_file_name(channel); unlink(file); free(file); #endif /*NOCLOSE*/ } if (debug) db("join_party: old channel closed\n"); } /* If we are to enter a new channel, finish the job */ if (nch != NULL) { /* Make the new file the current file */ rst= tmp_rst; wfd= tmp_wfd; tailed_from= 0L; /* Set streams to close on exec */ fcntl(rst,F_SETFD,1); fcntl(fileno(wfd),F_SETFD,1); /* Print Channel Banner */ if (channel != NULL) { if (opt[OPT_CHANINTRO].yes) printexec(stderr,opt[OPT_CHANINTRO].str); else fprintf(stderr, "\n================== channel %s ==================\n", newchannel); fprintf(stderr,"Options:"); if (printopts(stderr,8,' ',"chan")) fprintf(stderr,"normal\n\n"); else fputc('\n',stderr); } /* Get the user's name or pseudonym */ setname(newchannel); /* Put in a join message */ LOCK(wfd); fseek(wfd,0L,2); if (channel == NULL) fprintf(wfd,"---- %s joining (%.12s)\n", name,ctime(&now)+4); else fprintf(wfd,"---- %s joining from channel %s (%.12s)\n", name,channel,ctime(&now)+4); fflush(wfd); UNLOCK(wfd); /* Wind back a few lines */ lseek(rst,0L,2); /* Goto end of file */ backup(convert(opt[OPT_BACK].str)); strcpy(channel=ch,newchannel); who_chan(); } else who_exit(); return(0); } /* SETMAILFILE: Start monitering the user's mail directory so we can notify * him when he has new mail. */ setmailfile() { struct stat mailstat; char *env; if ((env= getenv("MAIL")) != NULL) strcpy(mailfile,env); else { strcpy(mailfile,opt[OPT_MAILDIR].str); strcat(mailfile,"/"); strcat(mailfile,realname); } if (stat(mailfile,&mailstat)) mailsize= 0L; else mailsize= mailstat.st_size; mailfuse= MAILDELAY; } /* LEAFNAME: Return the trailing name from a pathname. */ char *leafname(c) char *c; { register char *t; if ((t= strrchr(c,'/')) == NULL) return(c); else return(t+1); } /* BADNAME: Check a channel name to make sure it contains only legal * characters: namely printable, non-space, non-dot, non-slash characters. We * could probably stand dots, but we don't. */ badname(chn) char *chn; { char *badchr= " ./#"; for(;*chn;chn++) if (!isascii(*chn) || !isprint(*chn) || index(badchr,*chn)) return(1); return(0); } /* CONVERT: Convert a string to a non-negative integer. This does a bit more * checking than atoi(). It returns -1 if the string is not syntactially * correct. */ int convert(c) char *c; { register int n=0; /* Skip leading spaces and tabs */ while (*c == ' ' || *c == '\t') c++; for ( ; *c != '\n' && *c != '\0'; c++) { if (*c >= '0' && *c <= '9') n= n*10 + *c - '0'; else return (-1); } return(n); } #ifdef L_LOCKING /* Keep calling locking() until it works or until we get sick and tired of * calling it. This apparantly needs to be done because it doesn't block * properly. There may be problems with this. */ chk_lock(file,request) FILE *file; int request; { register int i= 5; while (locking(fileno(file), request, -1L) && i--) sleep(1); } #endif /*L_LOCKING*/ /* BACKUP: This seeks backwards by the given number of lines. */ long backup(lines) int lines; { #define BUB_SIZE 512 char bub[BUB_SIZE]; register char *p; long off; int n; if (lines <= 0) return; p= bub; off= lseek(rst,0L,1); /* Scan backwards, counting newlines */ do if (p-- == bub) { if (off == 0L) break; /* Fetch a new block of data */ off -= (n= (off > BUB_SIZE) ? BUB_SIZE : off); lseek(rst, off, 0); read(rst, bub, n); p= bub + n - 1; } while ( *p != '\n' || lines-- > 0); /* Position file pointer */ return(lseek(rst, off+(p-bub)+1, 0)); } /* INTR: Handle user interrupts. If he is tailing back or printing a help * file, just interrupt that. */ intr() { if (inhelp) { #ifdef SYSIII signal(SIGQUIT,(void (*)())intr); signal(SIGINT,(void (*)())intr); #endif /*SYSIII*/ longjmp(jenv,1); } if (tailed_from > lseek(rst,0L,1)) { #ifdef SYSIII signal(SIGQUIT,(void (*)())intr); signal(SIGINT,(void (*)())intr); #endif /*SYSIII*/ printf("\nTailback Interrupted...\n"); lseek(rst,tailed_from,0); return; } alarm(0); signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); join_party(NULL); STTY(0,&cooked); kill_filter(); exit(0); } /* TERM: Exit the program because we got a SIGTERM. This is like a hup(), but * prints a message to the user. */ term() { if (debug) db("killed\n"); alarm(0); signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); join_party(NULL); kill_filter(); STTY(0,&cooked); printf("\nParty Process Killed.\n"); exit(0); } /* DONE: Exit the program at the user's command. This should be used just * about everywhere you would normally call exit. */ done(rc) int rc; { if (debug) db("exiting\n"); alarm(0); signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); join_party(NULL); stop_filter(); STTY(0,&cooked); exit(rc); } /* HUP: Handle Hangups. The only difference betwen this and done() is that * we are more brutal about killing off the filter. */ hup() { if (debug) db("hangup\n"); alarm(0); signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); join_party(NULL); kill_filter(); STTY(0,&cooked); exit(0); } /* SUSP: Suspend execution temporarily. */ #ifdef BSD susp() { #ifdef F_TERMIOS struct termios old; #endif #ifdef F_STTY struct sgttyb old; #endif int mask; int was_shelled; if (debug) db("suspended\n"); if (!(was_shelled= who_isout())) who_shout("^"); alarm(0); mask= sigblock(sigmask(SIGTSTP)); GTTY(0,&old); STTY(0,&cooked); signal(SIGTSTP,SIG_DFL); sigsetmask(0); kill(getpid(),SIGTSTP); /* STOP HERE */ sigsetmask(mask); signal(SIGTSTP,(void (*)())susp); initmodes(); STTY(0,&old); if (!was_shelled) who_shin(); if (debug) db("restarted\n"); } #endif /*BSD*/ #ifdef VAR_ARGS vint db(char *msg, ...) { va_list ap; long now; if (debug) { va_start(ap, msg); now= time((long *)0); fprintf(debug,"%8.8s: ",ctime(&now)+11); vfprintf(debug,msg,ap); fflush(debug); } va_end(ap); } #else vint db(msg,arg1,arg2,arg3,arg4) char *msg, *arg1, *arg2, *arg3, *arg4; { long now; if (debug) { now= time((long *)0); fprintf(debug,"%8.8s: ",ctime(&now)+11); fprintf(debug,msg,arg1,arg2,arg3,arg4); fflush(debug); } } #endif /*VAR_ARGS*/ #ifdef VAR_ARGS vint err(char *msg, ...) { va_list ap; long now; va_start(ap, msg); fprintf(stderr,"%s error: ",progname); vfprintf(stderr,msg,ap); if (debug) { now= time((long *)0); fprintf(debug,"%8.8s: ",ctime(&now)+11); vfprintf(debug,msg,ap); fflush(debug); } va_end(ap); } #else vint err(msg,arg1,arg2,arg3,arg4) char *msg, *arg1, *arg2, *arg3, *arg4; { long now; fprintf(stderr,"%s error: ",progname); fprintf(stderr,msg,arg1,arg2,arg3,arg4); if (debug) { now= time((long *)0); fprintf(debug,"%8.8s: ",ctime(&now)+11); fprintf(debug,msg,arg1,arg2,arg3,arg4); fflush(debug); } } #endif /*VAR_ARGS*/