/* nwall -- chat-friendly wall program.
 * Copyright (C) 1999 Nick Moffitt
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.  
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <utmp.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <readline/readline.h>
#include <readline/history.h>

#define EVER ;;
#define IGNOREUSER "sleeper"

#ifndef LOGFILE
#define LOGFILE "/var/log/nwall.log"
#endif

#ifndef LOCKFILE
#define LOCKFILE "/var/lock/nwall/%s"
#endif

#ifndef LOCKDIR
#define LOCKDIR "/var/lock/nwall/"
#endif

/* forward declarations */
void NwallEnter(char *, char *);
void NwallExit(char *, char *);
struct iovec NwallFormatMessage(struct iovec, char *, char *);
struct iovec NwallFormatAction(struct iovec, char *, char *);
void NwallWriteAll(struct iovec, char *ttystring, int optedSelfOut);
void SignalHandler(int);
void OptOut(char *);
void OptIn(char *);
void NwallUsage(void);

char *ttymsg();


int main(int argc, char **argv)
{
    char *username, *ttystring, *ttyfilename;
    struct iovec message;
    int c;
	 int optedSelfOut = 0; /* When this is high, the user won't see msgs from himself */

    username = getlogin();
    ttyfilename = ttyname(2);
    ttystring = ttyfilename + 5*sizeof(char);

    while((c=getopt(argc, argv, "ynhs")) != EOF) {
		switch(c) {
	    case 'y':	OptIn(ttystring);
			return(0);
			break;
	    case 'n':	OptOut(ttystring);
			return(0);
			break;
	    case 's':	optedSelfOut = 1;
			break;
	    case 'h':
	    default:	NwallUsage();
			return(2);
			break;
		}
    }



    message.iov_base = (char *)NULL;
    message.iov_len = 0;


    /* Don't use tab-completion. */
    rl_bind_key('\t', rl_insert);

    /* Say hello to all the nice users */
    NwallEnter(username, ttystring);

    signal(SIGINT, SignalHandler);


    for(EVER) {
	/* If the buffer has already been allocated, return the memory
	 * to the free pool. 
	 */
	if (message.iov_base) {
	    free(message.iov_base);
	    message.iov_base = (char *)NULL;
	}
	/* Get a line from the user. */
	message.iov_base = (char *)readline ("nwall> ");

	/* If readline returned a null pointer (IE, ^D was pressed),
	 * exit with a pleasant message.
	 */
	if(!message.iov_base) {
	    NwallExit(username, ttystring);
	    exit(0); /* leave program */
	}

	/* since the pointer could have been null, we need to run
	 * strlen() after we check for that.
	 */
	message.iov_len = strlen((char *)message.iov_base) 
			+ sizeof(char);


	/* If the line has any text in it, save it on the history. */
	if ((char *)message.iov_base && *(char *)message.iov_base) {
	    add_history ((char *)message.iov_base);

	/* check for a leading '/' if so then this should be treated as a command 
	 * if (((char *)message.iov_base)[0] == '/') {
	 * printf("*********This is an action**********\n");
	 * 
	 * } 
	 */

		 
	/* Add attribution (eg "nick@ttyp1> hello!").
	 * Eventually we'll format for 80 columns using GNU fmt's
	 * functions.
	 */

	if ((char *)message.iov_base && !strncmp((char *)message.iov_base, "/me ", 4)) {
           message = NwallFormatAction(message, username, ttystring);
	} else {
      	    message = NwallFormatMessage(message, username, ttystring);
	}

	/* Go through the utmp and write the message to everyone
	 * logged in.  Also does logging and opt-outs.
	 */
	NwallWriteAll(message,ttystring,optedSelfOut);
    }
    }

} /* end of main() */

/*******************************************************************/


/* probably better to do these with a third string parameter and a
 * strncat somewhere, but I'm sleepy.
 */

/* print greeting message as user begins to wall */
void NwallEnter(char *username, char *ttystring)
{
    struct iovec hello;

    hello.iov_len = strlen(username) + strlen(ttystring) + sizeof(char)
	+ strlen(" has joined the nwall conversation.\a\r\n") +
	sizeof(char);
    hello.iov_base = (char *)malloc(hello.iov_len);

    snprintf((char *)hello.iov_base, hello.iov_len, 
	"%s@%s has joined the nwall conversation.\a\r\n", username,
	ttystring);

    NwallWriteAll(hello,ttystring,0);
		    
    free(hello.iov_base);
    return;
}

/* show that user has left the nwall program */
void NwallExit(char *username, char *ttystring)
{
    struct iovec eof;

    eof.iov_len = strlen(username) + sizeof(char) + strlen(ttystring) 
	+ strlen(" has left the building.\a\r\n") + 
	sizeof(char);
    eof.iov_base = (char *)malloc(eof.iov_len);

    snprintf((char *)eof.iov_base, eof.iov_len, 
	"%s@%s has left the building.\a\r\n", username, 
	ttystring);

    NwallWriteAll(eof,ttystring,0);

    free(eof.iov_base);
    return;
}

/* pretty-print the user's line of text, complete with allsortsfulla
 * info on username and tty (soon to include hostname if remote!)
 * Ultimately we'll use the functions from GNU fmt to format this to
 * 80 columns or something.
 */
struct iovec NwallFormatMessage(struct iovec inmsg, char *username, 
				char *ttystring)
{
    struct iovec outmsg;

    outmsg.iov_len = sizeof(char) + strlen(username) + sizeof(char)
	+ strlen(ttystring) + 2*sizeof(char) + inmsg.iov_len +
	2*sizeof(char);
    outmsg.iov_base = (char *)malloc(outmsg.iov_len);

    snprintf((char *)outmsg.iov_base, outmsg.iov_len, 
	" %s@%s> %s\r\n", username, ttystring, inmsg.iov_base);

    free(inmsg.iov_base);
    return outmsg;
}

/* pretty-print the user's line of text as an action (IRC-style /me)
 * message
 */
struct iovec NwallFormatAction(struct iovec inmsg, char *username, 
				char *ttystring)
{
    struct iovec action;
    struct iovec outmsg;

    action.iov_len = inmsg.iov_len - 4;
    action.iov_base = (char *)malloc(action.iov_len);
    strncpy(action.iov_base, inmsg.iov_base + 4, action.iov_len);

    outmsg.iov_len = 5 * sizeof(char) + strlen(username) + sizeof(char)
	+ strlen(ttystring) + sizeof(char) + action.iov_len +
	2*sizeof(char);
    outmsg.iov_base = (char *)malloc(outmsg.iov_len);

    snprintf((char *)outmsg.iov_base, outmsg.iov_len, 
	" *** %s@%s %s\r\n", username, ttystring, action.iov_base);

    free(inmsg.iov_base);
    free(action.iov_base);
    return outmsg;
}



/* write to all ttys (searching through utmp and excluding opt-outs
 * and the mysterious "sleeper") and do logging.
 */
void NwallWriteAll(struct iovec msg, char * ttystring, int optedSelfOut)
{
    struct utmp *utmpptr;
    struct stat optout;
    struct passwd *pwuid;
    size_t optoutlen;
    char *ttymsgerror, *optoutfile;
    int fd, writeverr;

    /* yeah, it's hackish, but sprintf prints the null, so we drop it here */
    msg.iov_len--;


    /* log the message right off the bat */
    fd = open(LOGFILE, O_WRONLY|O_APPEND|O_NONBLOCK);
    if((writeverr = writev(fd, &msg, 1)) == -1) {
	perror("nwall had a problem opening the log file");
    }
    close(fd);


    /* send one message to each logged-in user that hasn't opted out */
    setutent();

    /* For each entry in /var/utmp (each logged in user) */
    while(utmpptr = getutent()) {
	optoutlen = strlen(LOCKDIR) +
	    strlen(utmpptr->ut_line) + sizeof(char);
	optoutfile = (char *)malloc(optoutlen);
	snprintf(optoutfile, optoutlen, LOCKFILE,
		 utmpptr->ut_line);

	/* Here come all the reasons not to send the message to a
	 * particular user:
	 */

	/* opted out? (test is whether optout file is owned by current
	 * user for that tty)
	 */
	if(!stat(optoutfile, &optout)) {
	    pwuid = getpwuid(optout.st_uid);
	    if(!strcmp(utmpptr->ut_user, pwuid->pw_name)) {
		/* usernames match */
		continue;
	    }
	}
	free(optoutfile);
	
	
	/* valid utmp entry?  not the mysterious "sleeper" user? */
	if (!utmpptr->ut_name[0] || !strncmp(utmpptr->ut_name,
					     IGNOREUSER,
					     sizeof(utmpptr->ut_name)))
            continue;

	/* is this a proper user process? */
        if (utmpptr->ut_type != USER_PROCESS)
            continue;

	if (optedSelfOut && 
		 strncmp(utmpptr->ut_line,ttystring,strlen(ttystring)) == 0) {
			  continue;
	}

	/* Actually send the message! */
        if ((ttymsgerror = ttymsg(&msg, 1, utmpptr->ut_line, 60*5)) != NULL)
	    ;
	    /*
	    perror("nwall had a problem sending your message to a user");
	    */
    }

	/* close up our use of /var/utmp */
    endutent();

	return;
} /* end of NwallWriteAll */


void SignalHandler(int signal)
{
    if(signal == SIGINT) {
	NwallExit(getlogin(), ttyname(2) + 5*sizeof(char));
    }
    exit(signal);
}

void OptOut(char *ttyname)
{
    int optoutlen, filedes;
    char *optoutfile;
    struct stat optstat;

    optoutlen = strlen(LOCKDIR) + strlen(ttyname) +
	sizeof(char);
    optoutfile = (char *)malloc(optoutlen);
    snprintf(optoutfile, optoutlen, LOCKFILE, ttyname);
    if(!stat(optoutfile, &optstat)) {
	if(optstat.st_uid == getuid()) {
	    /* usernames match */
	    printf("You are already opted out: %s exists and is owned by you.\n", optoutfile);
	    free(optoutfile);
	    return;
	} else {
	    int retcode;

	    retcode = unlink(optoutfile);
	    if(retcode == -1) {
		fprintf(stderr, "\n Unable to remove stale opt-out file: %s\n Please mail your administrator.\n", optoutfile);
		exit(1);
	    }
	    printf("Stale opt-out file removed.\n");
	}
    }

    filedes = creat(optoutfile, 0660);
    if(filedes == -1) {
	fprintf(stderr, "\n Unable to create opt-out file: %s\n Try 'mesg n' instead!\n", optoutfile);
	exit(1);
    }
    printf("You are now opted out from receiving nwall messages.\nLockfile %s created.\n",optoutfile);
    free(optoutfile);
    close(filedes);
    return;

}


void OptIn(char *ttyname)
{
    int optoutlen, retcode;
    char *optoutfile;
    struct stat optstat;

    optoutlen = strlen(LOCKDIR) + strlen(ttyname) +
	sizeof(char);
    optoutfile = (char *)malloc(optoutlen);
    snprintf(optoutfile, optoutlen, LOCKFILE, ttyname);

    if(stat(optoutfile, &optstat) == -1){
	printf("You were already opted in!\n");
	free(optoutfile);
	return;
    }

    retcode = unlink(optoutfile);
    if(retcode == -1) {
	fprintf(stderr, "\n Unable to remove opt-out file: %s\n Try 'tail -f %s' instead!\n", optoutfile, LOGFILE);
	exit(1);
    }
    printf("You are now able to receive nwall messages.\n");
    free(optoutfile);
    return;

}

void NwallUsage()
{
    printf("nwall -- interactive wall-chat program\n");
    printf("\tnwall -y\tallow your terminal to receive nwall messages\n");
    printf("\tnwall -n\topt-out from receiving nwall messages\n");
    printf("\tnwall -s\topt-out from receiving your own nwall messages\n");
    printf("\tnwall -h\tthis message\n");
    return;
}
