/*
 * $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 <ctype.h>
#include <errno.h>
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<cmdlen && (*cp)[j] == cmd[i].name[j]; j++)
			;
		if (j==cmdlen && 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, &ltch);
#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);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1