/* * $Id: input.c,v 1.5 2002/08/23 13:38:14 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. */ /* PARTY PROGRAM -- USER INPUT ROUTINES - from keyboard to file - Jan Wolter */ #include "party.h" #include "opt.h" #include #include extern int errno; char *cancel= " XXX\n"; /* Cancel string */ char name[UT_NAMESIZE+2]; /* User's internal name or alias */ char realname[UT_NAMESIZE+2]= ""; /* User's real name (used for mail check) */ char logname[UT_NAMESIZE+2]; /* Name user logged in under (utmp) */ char logtty[18]; /* Tty user logged in to (utmp) */ long logtime; /* Time user logged in at (utmp) */ void cmd_colon(),cmd_scrollback(),cmd_join(),cmd_shell(),cmd_noise(); struct cc_tab { char cmd; /* command character that brings up prompt */ char prompt; /* prompt character brought up */ void (*func)(); /* procedure to call to interpret the command */ int enable; /* option that enables this command */ char *failmsg; /* message to print if not enabled */ } cmd_char[]={ /* TABLE OF INPUT MODE COMMAND CHARACTERS */ {':', ':', cmd_colon, OPT_COLON, NULL}, {'-', '-', cmd_scrollback, OPT_NONE, NULL}, {'#', '#', cmd_join, OPT_NONE, NULL}, {'!', '!', cmd_shell, OPT_SHELL, NULL}, {'/', '/', cmd_noise, OPT_MAKENOISE, "No noises allowed in this channel\n"}, /* End of cmd_char table */ { 0, 0, NULL, OPT_NONE, NULL}}; /* DOCMD: User has typed command character 'ch' in listening mode. Act on that. * Much of the behavior is driven by the table above. */ docmd(ch) int ch; { struct cc_tab *ccmd; if (ch == EOF_CHAR) done(0); /* Do Table Commands */ for (ccmd= cmd_char; ccmd->cmd != 0; ccmd++) if (ch == ccmd->cmd) { /* Check if command is enabled */ if (ccmd->enable == OPT_NONE || opt[ccmd->enable].yes) { /* Print the prompt */ STTY(0,&cooked); putc(ccmd->prompt,stderr); /* Read the command */ if (fgets(txbuf,BFSZ,stdin)) /* Execute the command */ (*ccmd->func)(txbuf); else { /* Input aborted */ fputs(cancel,stderr); clearerr(stdin); } STTY(0,&cbreak); } else if (ccmd->failmsg != NULL) err(ccmd->failmsg); else speak(ch); return; } /* Do non-table commands */ switch (ch) { case '=': /* Print options */ printopts(stderr,0,':',(char *)NULL); break; case 'q': /* quit -- unless we have firstchar */ if (opt[OPT_FIRSTCHAR].yes) speak(ch); else done(0); break; case 'h': /* help -- unless we have firstchar */ if (opt[OPT_FIRSTCHAR].yes) { speak(ch); break; } case '?': /* help */ if (opt[OPT_HELP].yes) help(opt[OPT_HELP].str,1); else speak(ch); break; default: /* input text */ speak(ch); break; } } /* SPEAK: Input a line from the user. Flag indicates if input mode was * invited by typing a space. */ speak(ch1) int ch1; { if (ch1 != ' ' && opt[OPT_SPACEONLY].yes) { fprintf(stderr,"%s\n",opt[OPT_SPACEONLY].str); return; } fputs(opt[OPT_PROMPT].str,stderr); if (getline(txbuf,BFSZ,ch1) && txbuf[0] != '\0') { fputs(opt[OPT_TPMORP].str,stderr); append(inbuf,wfd); } else { fputs(opt[OPT_TPMORP].str,stderr); fputs(cancel,stderr); clearerr(stdin); } } /* CMD_COLON: Handle a special command (: prefix). */ void cmd_colon(command) char *command; { if (debug) db("command %s\n",command); switch (command_code(&command)) { case CMD_SET: if (command) parseopts(command,0); else err("missing option on set command\n"); break; case CMD_READ: if (command) { *firstin(command," \t\n")= '\0'; readfile(command); } else err("read what file?\n"); break; case CMD_SAVE: if (command) { int n= -1; if (*command == '-') { if ((n= atoi(command+1)) <= 0) { err("inproper offset\n"); break; } command= firstout(firstin(command+1," \t")," \t"); } if (command) { savelog(n,command); break; } } err("save to what file?\n"); break; case CMD_BACK: if (command) { if (*command == '-') command++; cmd_scrollback(command); } else err("missing line count on back command\n"); break; case CMD_WHO: case CMD_PWHO: if (command == NULL) who_list(stderr,'c'); else if (command[0] != '-' || (command[1] != 'c' && command[1] != 't' && command[1] != 'n')) err("argument to :who must be -n, -t or -c\n"); else who_list(stderr,command[1]); break; case CMD_PRINT: printopts(stderr,0,':',command); break; case CMD_JOIN: if (command == NULL) err("no channel name given\n"); else { *firstin(command," \t\n")= '\0'; if (!strcmp(command,channel)) err("already on channel %s\n",txbuf); else join_party(command); } break; case CMD_HELP: help(opt[OPT_HELP].str,1); break; case CMD_CHANTAB: help(opt[OPT_CHANTAB].str,1); break; case CMD_NOISES: if (opt[OPT_MAKENOISE].yes) { fprintf(stderr,"legal noises:\n"); help(opt[OPT_MAKENOISE].str,1); } else err("No noises allowed in this channel\n"); break; case CMD_NAME: if (!opt[OPT_RENAME].yes) err("rename option not set in this channel\n"); else if (command == NULL) err("must give new name on :name command\n"); else { *firstin(command," \t\n")= '\0'; if (opt[OPT_UNIQUENAME].yes && !who_uniqalias(command, logname, channel) && strcmp(command,logname) && strcmp(command,realname)) err("the name %s is already in use\n",command); else changename(command); } break; case CMD_LIST: listchn(); break; #ifndef NOCLOSE case CMD_CLOSE: if (!opt[OPT_MAYCLOSE].yes) err("mayclose option not set in this channel\n"); else if (is_closed()) err("channel is already closed\n"); else close_chan(); break; case CMD_OPEN: if (!is_closed()) err("channel is already open\n"); else open_chan(); break; case CMD_INVITE: if (!is_closed()) err("this channel is open -- everyone is invited\n"); else if (command == NULL) err("must give a login id on :invite command\n"); else { char invitee[21]; *firstin(command," \t\n")= '\0'; strncpy(invitee,command,20); invitee[20]= '\0'; invite(invitee); } break; #endif /*NOCLOSE*/ #ifndef NOIGNORE case CMD_IGNORE: if (command == NULL) listignore(); else { char *e, ch; for (;;) { e= firstin(command," \t\n"); ch= *e; *e= '\0'; if (addignore(command)) err("you were already ignoring %s\n",command); else printf("Ignoring %s\n",command); if (ch == '\n' || ch == '\0') break; command= firstout(e+1," \t\n"); if (*command == '\0') break; } } break; case CMD_NOTICE: if (ignoring == NULL) err("no users were being ignored\n"); else if (command == NULL) { noignore(); printf("Un-ignoring all ignored users.\n"); } else { char *e, ch; for (;;) { e= firstin(command," \t\n"); ch= *e; *e= '\0'; if (delignore(command)) err("you were not ignoring %s\n",command); else printf("Un-ignoring %s\n",command); if (ch == '\n' || ch == '\0') break; command= firstout(e+1," \t\n"); if (*command == '\0') break; } } break; #endif /*NOIGNORE*/ case CMD_SHELL: if (command == NULL) err("no command given on :shell command\n"); else cmd_shell(command); break; case CMD_VERSION: fprintf(stderr,"party version %s - (c) 1990, Jan Wolter\n", version); break; case CMD_QUIT: done(0); default: fprintf(stderr,"No such command. Legal commands are:\n"); listcmds(stderr); break; } } /* Process a scrollback request */ void cmd_scrollback(tback) char *tback; { int n; if (debug) db("scrolling back %s\n",tback); if ((n= convert(tback)) < 0) err("tailback must be a number\n"); else { tailed_from= lseek(rst,0L,1); /* Current pos */ backup(n); } } /* Process a change-channel request */ void cmd_join(chn) char *chn; { *firstin(chn,"\n")= '\0'; if (chn[0] == '\0') listchn(); else if (!strcmp(chn,channel)) { err("already on channel %s\n",chn); } else join_party(chn); } /* Process a save -- if n is -1, save entire log. Otherwise save n lines */ savelog(n,filename) int n; char *filename; { FILE *fp; long length; int m; #define BS 1024 char bf[BS]; be_user(); *firstin(filename,"\n")= '\0'; if ((access(filename,2) && !access(filename,0)) || (fp= fopen(filename,"a")) == NULL) { err("Cannot open output file %s\n",filename); be_party(); return; } be_party(); fprintf(stderr,"%s to file %s...", ftell(fp) == 0L ? "Saving" : "Appending", filename); fflush(stderr); length= lseek(rst,0L,1); /* Current pos */ length-= (n < 0) ? lseek(rst,0L,0) : backup(n); /* New pos */ for (;;) { m= (length > BS) ? BS : length; if ((m= read(rst,bf,m)) < 0) break; fwrite(bf, 1, m, fp); if (m < BS) break; length-= BS; } fclose(fp); fprintf(stderr,"done\n"); } /* Process a change-channel request */ void cmd_noise(ncmd) char *ncmd; { *firstin(ncmd,"\n")= '\0'; if (ncmd[0] == '\0') { fprintf(stderr,"legal noises:\n"); help(opt[OPT_MAKENOISE].str,1); } else makenoise(ncmd); } /* LIST_CMDS: Print out a list of the legal : commands */ listcmds(fp) FILE *fp; { int i,col=0,len; int cols= convert(opt[OPT_COLS].str); for (i= 0; i < NCMD; i++) { len= strlen(cmd[i].name); if (col+len >= cols) { fputc('\n',fp); col= 0; } col+= len+1; fprintf(fp," %s",cmd[i].name); } fputc('\n',fp); } /* COMMAND_CODE: Returns the command code of the given command string. * If it was a valid command, the pointer is advanced to point to the first * argument, or is set to NULL if there are none. Otherwise, if it is not * a legal command, -1 is returned and *cp still points to the command word. */ int command_code(cp) char **cp; { int cmdlen; char *arg1; int i; int j; /* Skip leading spaces */ *cp= firstout(*cp," \t"); if (**cp == '!') { (*cp)++; return(CMD_SHELL); } /* Find end of first word */ cmdlen= (arg1= firstin(*cp," \t\n"))-*cp; if (cmdlen == 0) return(-1); /* Find start of second word */ for (i= 0; i< NCMD; i++) { for (j= 0; j=cmd[i].abbr) { /* Find first argument */ *cp= firstout(arg1," \t"); if (**cp == '\0' || **cp == '\n') *cp= NULL; return(i); } } return(-1); } /* SETREALNAME: Get the user's realname. logname should already have been * set to the the name he was logged in under (as given in the utmp file). * The realname is basically the user who owns the current party process (this * is often different from logname if the user has done an "su"), but if there * is more than one entry in the passwd file with a uid matching this process, * then the login name is prefered. If there is no passwd file entry matching * this process's uid, then the name is his id number. */ setrealname() { struct passwd *pwd; char *tname; /* if multiple names with same uid's use the one in utmp */ if ((pwd= getpwnam(logname)) != NULL && pwd->pw_uid == getuid()) strcpy(realname,logname); else { if (pwd= getpwuid(getuid())) strcpy(realname, pwd->pw_name); else sprintf(realname, "%d", getuid()); } } /* SETNAME: Put the user's name (possibly fake) in the global name variable. * This is called every time we change channels. The name of the channel we * are joining is passed as an argument. */ setname(chan) char *chan; { FILE *fp; char *ch; int i, havename; if (opt[OPT_UIDNAME].yes) strcpy(name, opt[OPT_RENAME].yes ? opt[OPT_ALIAS].str : realname); else { /* take name from utmp file */ if (ch= getlogin()) strcpy(name, ch); else strcpy(name, "(unknown)"); } havename= 0; if (opt[OPT_MAPNAME].yes && opt[OPT_MAPNAME].str[0] != '\0') { /* change the user's name according to the mapping in the file */ if ((fp= fopen(opt[OPT_MAPNAME].str,"r")) == NULL) err("cannot open mapname file %s\n",opt[OPT_MAPNAME].str); else { while (fgets(txbuf,BFSZ,fp) != NULL) { *(ch= firstin(txbuf," \t"))= '\0'; if (!strcmp(realname,txbuf)) { ch= firstout(ch+1," \t"); *firstin(ch," \n\t")= '\0'; strncpy(name,ch,UT_NAMESIZE); name[UT_NAMESIZE]= '\0'; havename= 1; break; } } } } if (!havename) { if (opt[OPT_RANDNAME].yes && opt[OPT_RANDNAME].str[0] != '\0') { /* Pick a name from a random line of the named file */ if ((fp= fopen(opt[OPT_RANDNAME].str,"r")) == NULL) err("cannot open randname file %s\n",opt[OPT_RANDNAME].str); else { /* Initialize random number generator, adding some garbage to the second so two users entering at the same time don't get the same name */ SEEDRAND((unsigned)(time((long *)0) + realname[2] + 50*realname[1] + 2500*realname[0])); i = 1; name[UT_NAMESIZE]= '\0'; while (fgets(txbuf,BFSZ,fp) != NULL) { if (txbuf[0] != '#' && (unsigned)RAND() <= (unsigned)MAXRAND / (i++)) { *firstin(txbuf,": \n\t")= '\0'; strncpy(name,txbuf,UT_NAMESIZE); name[UT_NAMESIZE]= '\0'; } } } } else if (opt[OPT_ASKNAME].yes) { /* Prompt user for name -- For halloween parties */ for (;;) { fprintf(stderr,"What name would you like (8 chars max)? "); fgets(txbuf,BFSZ,stdin); /* If none given, use real name */ if (txbuf[0] == '\n') { if (opt[OPT_RENAME].yes) { /* Use his alias if it is unique */ strcpy(name, opt[OPT_ALIAS].str); checkname(name); if (who_uniqalias(name, logname, chan)) break; } /* He can always use his real name, unique or not */ strcpy(name, realname); break; } else { checkname(txbuf); if (strcmp(txbuf,logname) && strcmp(txbuf,realname) && !who_uniqalias(txbuf, logname, chan)) fprintf(stderr,"That name is already in use\n"); else { strncpy(name,txbuf,UT_NAMESIZE+1); break; } } } } } checkname(name); stashname(); } /* CHANGENAME: change the user's name to the given string and append a message * telling about it to the user's current channel. */ changename(newname) char *newname; { char onmbuf[UT_NAMESIZE+2]; /* Save the old name */ strcpy(onmbuf,name); /* Set the new name */ strncpy(name,newname,UT_NAMESIZE+1); checkname(name); /* Stash the name in the input buffer */ stashname(); /* Print the message */ /* If you change the format of this at all, edit ignore_line() too */ sprintf(txbuf, "~~~~ %s turns into %s\n",onmbuf,name); append(txbuf,wfd); /* Record it in the partytmp file */ who_chan(); } /* STASHNAME: copy the global variable "name" into the input buffer. Should * be called everytime name is changed. Warning, this may redefine the txbuf * pointer as a side effect. */ stashname() { int i, namelen, headlen; /* Figure out length of header field of line */ namelen= strlen(name); if (namelen < 8) /* UT_NAMESIZE would be ugly here */ headlen= 10; else headlen= namelen+2; txbuf= inbuf+headlen; /* Point txbuf to body part of line */ /* Stash the name in the input buffer with ':' and spaces after it */ strcpy(inbuf,name); inbuf[namelen]= ':'; for (i= namelen + 1; i < headlen; i++) inbuf[i]= ' '; } /* CHECKNAME: clean up a name's syntax. Currently we don't allow upper case * or non-printable characters. It may only be UT_NAMESIZE characters long. * No 0 length names (Apologies to Ryan Antkowiak, who found that bug and * wanted to keep it). No names starting with space, = or - or <, since those * are used to recognize a line as a read, event or noise line. */ checkname(name) char *name; { int i; for (i= 0;i < UT_NAMESIZE;i++) { if (name[i] == '\n' || name[i] == '\0') break; else if (!isprint(name[i])) name[i]= '#'; else if (isupper(name[i])) name[i]= tolower(name[i]); } /* Fix null names */ if (i == 0) name[i++]= '_'; name[i]= '\0'; /* Fix names starting with cue characters */ if (name[0] == ' ' || name[0] == '<' || name[0] == '-' || name[0] == '~') name[0]= '_'; } /* cmd_shell: execute a shell escape */ void cmd_shell(buf) char *buf; { *firstin(buf,"\n")= '\0'; who_shout(buf); usystem(buf); fputs("!\n",stderr); initmodes(); /* Reread ttymodes - may change */ who_shin(); } /* MAKENOISE: Look up a noise and stick it in the file */ makenoise(cmd) char *cmd; { FILE *fp; int cmdlen; int nargs,cargs=0; #define MAXNARGS 10 char *arg[MAXNARGS]; char *p,*q; char nbuf[BFSZ]; char rbuf[BFSZ]; int i,j; /* Open noise file */ if ((fp= fopen(opt[OPT_MAKENOISE].str,"r")) == NULL) { err("cannot open noisetab %s\n",opt[OPT_MAKENOISE].str); return(1); } /* Get command word length */ *firstin(cmd,"\n")= '\0'; cmd= firstout(cmd," \t"); cmdlen= firstin(cmd," \t") - cmd; /* Count arguments and save their starting positions */ nargs= 0; p= firstout(cmd + cmdlen," \t"); while (*p != '\0' && nargs < MAXNARGS) { arg[nargs++]= p; p= firstout(firstin(p," \t")," \t"); } /* Find the first matching line in the file */ while (fgets(rbuf,BFSZ,fp) != NULL) { if (!strncmp(cmd,rbuf,cmdlen) && (rbuf[cmdlen] == ' ' || rbuf[cmdlen] == '\t')) { p= firstin(rbuf+cmdlen+1,"0123456789"); if ((cargs= atoi(p)) > nargs) continue; p= firstin(p,"<"); if (*p == '\0') continue; /* Generate noise from template and put it in nbuf */ i= 0; nbuf[i++]= '<'; /* Insert tag */ for (q= name; *q != '\0'; q++) nbuf[i++]= *q; nbuf[i++]= ':'; /* Interpret noise definition */ for (p++ ;*p != '\0' ;p++) { if (*p != '$') nbuf[i++]= *p; else { j= atoi(++p); p= firstout(p,"0123456789")-1; if (j > cargs) continue; for (q= (j==0)?name:arg[j-1]; *q != '\0' && *q != '\n' && (j == cargs || (*q != ' ' && *q != '\t')); nbuf[i++]= *(q++)); } } nbuf[i]= '\0'; /* Put noise in the party file */ append(nbuf,wfd); fclose(fp); return(0); } } if (cargs > 0) err("need %d argument%s\n", cargs,(cargs == 1)?"":"s"); else err("no such noise\n"); fclose(fp); return(0); } /* APPEND: Output a string to the end of the party log file */ append(string,wfd) char *string; FILE *wfd; { LOCK(wfd); fseek(wfd,0L,2); fputs(string,wfd); if (strchr(string,'\n') == NULL) fputc('\n',wfd); fflush(wfd); UNLOCK(wfd); } /* INITMODES: Set up the cooked and cbreak modes for future use. Note that * this doesn't actually change the modes. */ initmodes() { /* Get ioctl modes */ errno= 0; if (debug) db("reading stty modes\n"); GTTY(0,&cooked); #ifdef DEBUG_STTY if (debug) {db("cooked modes read\n");printstty(debug,&cooked);} #endif /*DEBUG_STTY*/ #ifdef F_STTY ioctl(0, TIOCGETC, &tch); #ifdef TIOCGLTC ioctl(0, TIOCGLTC, <ch); #endif #endif /*F_STTY*/ if (errno != 0) { err("Input not a tty\n"); exit(1); } cbreak= cooked; /* Structure assignment works? */ #if defined(F_TERMIO) || defined(F_TERMIOS) cbreak.c_lflag&= ~(ECHO | ICANON); cbreak.c_cc[VMIN]= 1; cbreak.c_cc[VTIME]= 0; #endif /*F_TERMIO or F_TERMIOS*/ #ifdef F_STTY cbreak.sg_flags &= ~ECHO; cbreak.sg_flags |= CBREAK; #endif /*F_STTY*/ #ifdef DEBUG_STTY if (debug) {db("cbreak modes set up\n");printstty(debug,&cbreak);} #endif /*DEBUG_STTY*/ } /* GETLINE -- read in a line from the user. If ch1 is not EOF and the * firstchar option is set, it will be the first character read. The * interfaces is vaguely fgetslike since it returns the newline in the * string. */ char *getline(bf,n,ch1) char *bf; int n; int ch1; { int col,ch,i; char *rc; if (isascii(ch1) && isprint(ch1) && opt[OPT_FIRSTCHAR].yes) { /* Read in cbreak mode, doing as much of our own cooking as possible */ putchar(ch1); bf[0]= ch1; col= 1; for(;;) { ch= getchar(); if (ch == '\t') ch= ' '; /* Tabs are a pain--get rid of them */ if (ch == '\n') { bf[col++]= ch; bf[col]='\0'; putchar(ch); return(bf); } else if (ch == EOF_CHAR) { if (col == 0) return(NULL); putchar('\007'); } else if (ch == ERASE_CHAR) { if (col == 0) continue; col--; putchar('\b'); putchar(' '); putchar('\b'); } else if (ch == KILL_CHAR) { while (col > 0) { col--; putchar('\b'); putchar(' '); putchar('\b'); } } else if (ch == WERASE_CHAR) { while (col > 0 && bf[col-1] == ' ') { putchar('\b'); col--; } if (col == 0) continue; col--; putchar('\b'); putchar(' '); putchar('\b'); while (col > 0 && bf[col-1] != ' ') { col--; putchar('\b'); putchar(' '); putchar('\b'); } } else if (ch == REPRINT_CHAR) { putchar('\n'); for (i= 0; i < col; i++) putchar(bf[i]); } else if (isascii(ch) && isprint(ch) && col < n-2) { bf[col++]= ch; putchar(ch); } else putchar('\007'); } } else { /* Read in cooked mode ... the good old fashioned party style */ STTY(0,&cooked); rc= fgets(txbuf,BFSZ,stdin); STTY(0,&cbreak); return(rc); } }