/*
 * $Header: /source/users/msg/msg/RCS/msg.c,v 1.24 91/08/18 18:37:29 dgharriss Exp $
 */

/*
 * msg.c
 * MSG re-written to use a daemon
 * John Sellens, University of Waterloo
 */

#include "msg.h"
#include <pwd.h>
#if vms
#undef VARARGS
#include <string.h>
#include syidef
#include ssdef
#else
#include <strings.h>
#endif
#ifdef REALUSER
#include <mfcf/libuw/standard.h>
#endif

/* global */ char	hostname[80];
private	char	*progname;
#define USERIDSPACE	(80)	/* lots of room */
private	char	sender[USERIDSPACE];
private	char	loginname[USERIDSPACE];
private	int	uid;	/* effective */
private	int	ruid;	/* real */

private	char 	msg_file[80];
private	char 	pmsg_file[80];

extern	char	*syserr();
extern	char	*ttyname();

private	int	getsender(), sendmsg();
private	void	replywarn();
private	void	usage();
private	void	getprevious();
private	void	putprevious();
extern	void	fatal();

#define	SU	(0)	/* uid of super-user */

main( argc, argv )
int argc;
char *argv[];
{
    register int i;
#if vms
    register int exitcode = 1;
#else
    register int exitcode = 0;
#endif
    int fromroot;
    int useprevious = 0;	/* send previous text as msg */
    int cmdlinemsg = 0;		/* msg is on cmd line */
    int	iargv = 0;
    char *p;
    char message[1024];			/* to hold message */
    char rawmsg[1024];			/* to hold unclean message */
    char *tname;			/* name of current tty */
    char *reply = NULL, *reply_to();
    int silent;

    int c;
    extern int optind;
    extern char *optarg;
#if vms
typedef struct {
    short len;
    short code;
    int     addr;
    int     len_addr;
} ss_list;

    ss_list syilst[2];
    char    *cp;
    char    node_name[16];
    short   node_name_len;
#endif

    progname = argv[0];
    if ( argc == 1 )
	usage();

    if ( gethostname( hostname, (int)sizeof(hostname) ) )
	fatal( "Couldn't gethostname(): %s", syserr() );
#if vms
    if (!strcmp(hostname, "[unknown]")) {
	syilst[0].len = 15;
	syilst[0].code = SYI$_NODENAME;
	syilst[0].addr = &node_name;
	syilst[0].len_addr = &node_name_len;
	syilst[1].len = 0;
	syilst[1].code = 0;
	sys$getsyi(NULL, NULL, NULL, &syilst, NULL, NULL, NULL);
	node_name[node_name_len] = '\0';
	for (cp = &node_name; *cp != '\0'; cp++) {
	    *cp = tolower(*cp);
	}
	strcpy(hostname, node_name);
    }
#endif

    fromroot = getsender();
    strcpy( msg_file, MSGFILE );
    strcat( msg_file, loginname );
    strcpy( pmsg_file, PMSGFILE );
    strcat( pmsg_file, loginname );
    silent = 0;
    rawmsg[0] = '\n';
    rawmsg[1] = '\r';

    while (( c = getopt(argc, argv, "lm:prst")) != EOF ) {
	switch( c ) {
	case 'l':
	    list_msg();
	    break;
	case 'm':
	    strcpy( &rawmsg[2], optarg ); /* hope they don't have a giant msg */
	    cmdlinemsg++;
	    break;
	case 'p':
	    useprevious++;
	    break;
	case 'r':
	    if ( (reply = reply_to()) == NULL ) {
		fatal( "Nobody to reply to in last message file '%s'",
		    msg_file );
	    }
	    break;
        case 's':
	    silent++;
	    break;
	case 't':
	    list_msgtime();
	    break;
	default:
	    usage();
	}
    }
    if ( useprevious && cmdlinemsg )
	fatal( "-m and -p can't be used together" );

    /* Tell 'em now so that the lastmsg comes before you get the replying
       message, and tell them so they know who they're sending to, in
       case it's wrong. */
    if ( reply != NULL )
	errprintf("Replying to '%s'", reply);

    /*
     * Simpler to pretend the options weren't there. Skip over 'em.
     * argv/argc is now a vector of user names.
     */
    argc -= optind;
    argv += optind;


    if ( (cmdlinemsg||useprevious) && reply == NULL && argc == 0 )
	fatal( "No one to send message to" );

    /*
     * Quietly exit if no recipients and no reply.  This covers the case
     * where all we got was the -l option.
     */
    if ( reply == NULL && argc == 0 )
#if vms
	exit(1);
#else
	exit(0);
#endif

    /* Old behaviour used - as last argument to indicate msg on stdin */
    if ( strcmp("-", argv[argc-1]) == 0 ) {
	errprintf( "'-' as last argument is obsolete - ignored" );
	argc--;
    }

    if ( useprevious ) {
	getprevious( rawmsg, (int)sizeof(rawmsg) );
    } else if ( ! cmdlinemsg ) {
	if ( fgets( rawmsg, (int)sizeof(rawmsg), stdin ) == NULL ) {
	    /* assume user changed their mind */
#if vms
	    return(1);
#else
	    return(0);
#endif
	}
	p = index(rawmsg, '\n');
	if (p == NULL)
	    errprintf("Message truncated to %d characters.", sizeof(rawmsg) );
	else
	    *p = NULL;
    }

    if ( *rawmsg == '\0' )	/* No message to send. */
#if vms
	return(1);
#else
	return(0);
#endif
    sanitize( rawmsg, message );

    if ( useprevious )
	printf( "Sending: %s\n", message );
    else
	putprevious( message );

    if( (tname=ttyname(0)) || (tname=ttyname(1)) || (tname=ttyname(2)) )
	replywarn(tname);	/* warn if can't get replies on this tty */
#if 0
    /* Is this a good idea?  -IAN! */
    else
	errmsg( NO_TTY );
#endif

    /*
     * Send a reply if -r was used, also send to all other
     * recipients on the command line.
     */

    if ( reply != NULL && sendmsg(reply, message, fromroot) != OK )
	exitcode++;

    while ( --argc >= 0 ) {
	/* sendmsg() will destroy the argv[i] */
#if vms
	/*
	 * Ha Ha -- stoopid VAX C!
	 */
	if ( sendmsg( *argv++, &message, fromroot, silent ) != OK )
#else
	if ( sendmsg( *argv++, message, fromroot, silent ) != OK )
#endif
	    exitcode++;
    }
#if vms
    return( 1 );
#else
    return( exitcode!=0 );
#endif
}


/*
 * Find out who the sender is.  Try getlogin(), and if that
 * differs from the REAL uid, make sure both names appear.
 * Return 1 if we are root, 0 otherwise.
 */
static int
getsender()
{
#if vms
    char *glog;
    glog = getenv("USER");
    if (glog) strcpy(loginname, glog);
    else strcpy(loginname, "");
    (void) sprintf(sender, "%s@%s", loginname, hostname);
    return( 0 );

#else
    extern char *getlogin(), *getenv();
    struct passwd *pw;
    char pwname[USERIDSPACE];
    char *glog;

    ruid = uid = getuid();

    /* Check out who we're running as */
    if ( (pw=getpwuid(uid)) == NULL )
	fatal("Your uid number (%d) isn't recognized.\n", uid);
    if(pw->pw_name == NULL || pw->pw_name[0] == '\0')
	fatal("No pwname is associated with uid %d.\n", uid);
#ifdef REALUSER
    (void) strcpy( pwname, realuser( pw ) );
#else
    (void) strcpy( pwname,  pw->pw_name );
#endif

    /* see if we can use getlogin() to see who it is */
    (void) strcpy( loginname, (glog=getlogin()) == 0 ? "" : glog );
    if ( *loginname ) {
	/* check the passwd file for this guy */
	pw = getpwnam( loginname );
	if ( pw ) {
	    ruid = pw->pw_uid;
#ifdef DUMBSHORT
	    {
		struct utmp *ut;
		if ( strncmp( pwname, loginname, sizeof(ut->ut_name) ) == 0 )
		    (void) strcpy( loginname, pwname );
	    }
#endif
#ifdef REALUSER
	    (void) strcpy( loginname, realuser( pw ) );
#endif
	}
    } else {
	/* no better guess at real identity, use USER if we're root */
	if ( uid == SU && (glog=getenv("USER"))!=(char *)NULL && *glog ) {
	    /* look up the pw entry for USER */
	    if ( (pw=getpwnam(glog)) != (struct passwd *)NULL ) {
		ruid = pw->pw_uid;
#ifdef REALUSER
		glog = realuser( pw );
#endif
	    }
	    strcpy( loginname, glog );
	} else
	    strcpy( loginname, pwname );
    }

    if (
#ifdef REALUSER
        ! sameuser( pwname, loginname )
#else
	strcmp( pwname, loginname ) != 0
#endif
	&& uid != SU ) {
	/* Names differ, and user isn't SU, so send both */
	(void) sprintf(sender, "%s(%s)@%s", loginname, pwname, hostname);
    } else {
	(void) sprintf(sender, "%s@%s", loginname, hostname);
    }

    return( uid == SU );
#endif
}


private int
sendmsg( recipient, message, fromroot, silent )
char *recipient, *message;
int fromroot;
int silent;
{
    register char *p, *user, *host;
#ifndef REMOTE
    int local = FALSE;
#endif
    int errcode;
    if ( (p = index( recipient, '@' )) != CPNULL ) {
	user = recipient;
	host = p+1;
	*p = '\0';
    } else if ( (p = index( recipient, '!' )) != CPNULL ) {
	user = p+1;
	host = recipient;
	*p = '\0';
    } else {
	user = recipient;
	host = hostname;
    }
/* always go via the daemon, because it means we don't have to be
  setuid, and it makes things a little bit simpler.  I hope - jms.
  wrong - jms */
#ifndef REMOTE
    /* this only works if they use the full host name, not an alias, but
       that's okay ... */
    if ( strcmp( host, hostname ) == 0 )
	local = TRUE;
    if ( local )
	errcode = deliver( sender, user, message, fromroot );
    else
#endif
	errcode = remote( sender, user, host, message, silent );
    return( errcode );
}



/* Warn user on tname if he/she can't receive messages.
 */
private void
replywarn(tname)
    char *tname;
{

#if vms
#else
    struct utmp ut;
    struct stat sbuf;
    FILE *fp;
    char *p;
    char terminal[80];	/* should be enough ... */

    if( (fp=fopen( UTMP, "r" )) == FPNULL ) {
	errmsg( NO_UTMP, hostname, syserr() );
    } else {
	if( (p=rindex(tname,'/')) != NULL )
	    tname = p + 1;
	while ( fread((char*)&ut,sizeof(struct utmp),1,fp) != 0 ){
	    if( strncmp(tname,ut.ut_line,(int)sizeof(ut.ut_line)) == 0 ){
		(void) sprintf( terminal, "%s%s", DEV, tname );
		if ( stat(terminal,&sbuf)!=-1 && ((sbuf.st_mode&ALLOW)==0) )
		    errmsg( NO_REPLY, loginname, tname );
#ifdef ANSWERBACK
		answerback( ANS_WARN, loginname, tname );
#endif
		break;	/* only need to check the first match */
	    }
	}
	(void) fclose( fp );
    }
#endif
}


private void
usage()
{
    errprintf( "Usage: %s [-l] [-m message] [-p] [-r] [-t] [user ...]",
	progname );
    exit( BAD_ARGS );
}


#ifdef VARARGS
errprintf( s, va_alist )
char *s;
va_dcl
{
    va_list ap;
    (void) fprintf( stderr, "%s: ", progname );
    va_start( ap );
    (void) vfprintf( stderr, s, ap );
    va_end( ap );
    (void) fputc( '\n', stderr );
}

void
fatal( s, va_alist )
char *s;
va_dcl
{
    va_list ap;
    (void) fprintf( stderr, "%s: ", progname );
    va_start( ap );
    (void) vfprintf( stderr, s, ap );
    va_end( ap );
    (void) fputc( '\n', stderr );
    exit( FATAL );
}

/* VARARGS1 */
errmsg( msgnum, va_alist )
int msgnum;
va_dcl
{
    va_list ap;
    (void) fprintf( stderr, "%s: ", progname );
    va_start( ap );
    (void) vfprintf( stderr, errmessages[msgnum], ap );
    va_end( ap );
    (void) fputc( '\n', stderr );
}
#else
/* VARARGS1 */
errprintf( a, b, c, d, e, f, g, h, i, j, k )
char *a;
{
    (void) fprintf( stderr, "%s: ", progname );
    fprintf( stderr, a, b, c, d, e, f, g, h, i, j, k );
    (void) fputc( '\n', stderr );
}

/* VARARGS1 */
void
fatal( a, b, c, d, e, f, g, h, i, j, k )
char *a;
{
    (void) fprintf( stderr, "%s: ", progname );
    fprintf( stderr, a, b, c, d, e, f, g, h, i, j, k );
    (void) fputc( '\n', stderr );
    exit( FATAL );
}

/* VARARGS1 */
errmsg( msgnum, a, b, c, d, e )
int msgnum;
char *a, *b, *c, *d, *e;
{
    errprintf( errmessages[msgnum], a, b, c, d, e );
}
#endif /* VARARGS */

list_msgtime()
{
    /*
     * List time last msg was received.
     */
    struct stat statbuf;

    if (stat(msg_file, &statbuf) == -1)
	errprintf("can't get date of message file '%s': %s",
	    msg_file, syserr());
    else
	printf("last message received %s", ctime(&statbuf.st_mtime));
}


list_msg()
{
    /*
     * List message to which we are replying.
     * Just cat the last msg file.
     */

    FILE *fp;
    int c;
	    
    if ( (fp = fopen(msg_file, "r")) == NULL ) {
	errprintf("Cannot read last message file '%s'", msg_file );
    } else {
	while ( (c = getc(fp)) != EOF ) {
	    putchar(c);
	}
    }
}


private void
getprevious( buf, size )
char *buf;
int size;
{
    struct stat sbuf;
    FILE *pfp;
    int count;
    if ( stat( pmsg_file, &sbuf ) == -1 )
	fatal( "Could not stat previous message file '%s': %s",
	    pmsg_file, syserr() );
#if vms
#else
    if ( sbuf.st_uid != ruid )
	fatal( "You do not own previous message file '%s'", pmsg_file );
#endif
    if ( (pfp = fopen( pmsg_file, "r" )) == (FILE *)NULL )
	fatal( "Could not fopen previous message file '%s' for read: %s",
	    pmsg_file, syserr() );
    if ( fgets( buf, size, pfp ) == NULL )
	fatal( "Could not read previous message from file '%s' (empty?)",
	    pmsg_file );
    /* In case the file didn't end with a \n, we want to make sure that
       we don't complain above that it has been truncated.  This is kind
       of gross. */
    if ( strlen( buf ) < size )
	strcat( buf, "\n" );
    (void) fclose( pfp );
}

private void
putprevious( msg )
char *msg;
{
    FILE *pfp;
#if vms
    (void) delete( pmsg_file );     /* in case it already exists */
#endif
    if ( (pfp = fopen( pmsg_file, "w" )) == (FILE *)NULL ) {
	errprintf( "Could not fopen previous message file '%s' for write: %s",
	    pmsg_file, syserr() );
    } else {
	fprintf( pfp, "%s\n", msg );
	fclose( pfp );
	chown( pmsg_file, ruid, -1 );
	chmod( pmsg_file, 0600 );
    }
}




/*
 * reply_to - look in MSGFILE to see who sent us
 *  the last message.
 */

char *
reply_to()
{
    register char *p;
    static char buf[80];	/* Static 'cause we return what's in it. */
    char userfrom[40], whocares[40], hostfrom[40];
    FILE *f;

    if ( (f = fopen(msg_file, "r")) == NULL ) {
	return( NULL );
    }

    if ( fgets(buf, sizeof(buf),f) == NULL )
	return( NULL );
    /*
     * The file can look like
     * user@host: msg
     * or
     * user(somebody)@host: msg
     * Scan this in a basically cheap way.
     * I suppose some goofball exceptions could slip through
     * here if people had () in their login or host name, blah.
     */
    

    if ( (p = index(buf, ':')) == NULL ) {
	return( NULL );    /* Bad format. */
    }
    *p = '\0';

    /*
     * Now see which format we have.  Strip out intermediate name.
     * there are probably more rigorous ways to do this
     */
    p = index(buf, '(');

    if ( p != NULL ) {
	*p++ = '\0';		/* First part is username */
	p = index(p, ')');	/* Skip middle bit. */
	if ( p == NULL ) {
	    return(NULL);	/* Bad format */
	}

	if ( *++p != '@' )
	    return(NULL);

	strcat(buf, p);		/* Tack on hostname. */
    }

    return( buf );
}
