/* xdmutmp: add utmp entries for xdm clients
   $Id: xdmutmp.c,v 1.4 1997/04/02 14:13:17 tjchol01 Exp $
   Author: Tomasz J. Cholewo (t.cholewo@ieee.org)
 */

#include <stdio.h>
#include <string.h>                             /* memset */
#include <stdlib.h>                             /* malloc */
#include <unistd.h>                             /* getopt */
#include <fcntl.h>                              /* open, write */
#include <time.h>                               /* time */
#include <pwd.h>                                /* getpwnam */
#include <utmp.h>
#include <utmpx.h>
#include <lastlog.h>
#include <syslog.h>

#define PROGNAME "xdmutmp"

/* size of |utmpx.ut_id| */
#define ID_LEN 4

/* Has to end with a slash!  */
#define LASTLOG_DIR "/var/adm/lastlog/"
/* Must be at least length of |LASTLOG_DIR| and the longest |user_name|.  */
#define LLOG_LEN 100

#ifdef DEBUG
#undef UTMP_FILE
#define UTMP_FILE "./utmp"
#undef WTMP_FILE
#define WTMP_FILE "./wtmp"

#undef UTMPX_FILE
#define UTMPX_FILE "./utmpx"
#undef WTMPX_FILE
#define WTMPX_FILE "./wtmpx"

#undef LASTLOG_DIR
#define LASTLOG_DIR "./lastlog/"
#endif

int min (int a, int b) {
    return (a < b) ? a : b;
}

/* |line| field is constructed by primitive hashing of host name and
   representing it in "base64" encoding.  */
char *
hash_id (const char *host) {
    static char id[ID_LEN + 1];
    const char *p;
    int i, h = 0;
    for (p = host; *p; p++)
        h = (256 * h + *p) % 104729;            /* 64^3 = 262144 */
    for (i = 0; i < ID_LEN - 1; i++) {
        id[i] = (h % 64) + 32;
        h /= 64;
    }
    id[ID_LEN] = '\0';
    return id;
}

int
main (int argc, const char **argv) {
    /* options and arguments */
    int dflag;
    const char *user_name;
    const char *host_name;
 
    struct utmp utmp_entry;                     /* For |sizeof| only */
    struct utmpx utmpx_entry;
    struct passwd *pwd;
    const char *id;
    char llog_name[LLOG_LEN];
    int llog;

    openlog (PROGNAME, LOG_PERROR, LOG_USER);
    
    if (argc < 4 || argv[1][0] != '-') {
        syslog (LOG_INFO, "Usage: " PROGNAME " -a|-d user display");
        return 1;
    }

    switch (argv[1][1]) {
    case 'a':
        dflag = 0;
        break;
    case 'd':
        dflag = 1;
        break;
    default:
        syslog (LOG_ERR, "Bad option `%c'", argv[1][1]);
        return 1;
    } 
    user_name = argv[2];
    host_name = argv[3];

    /* |user_name| must exist in /etc/passwd.  */
    if (!(pwd = getpwnam (user_name))) {
        syslog (LOG_ERR, "User `%s' unknown", user_name);
        return 1;
    }

    /* Initialize utmp/utmpx records.  */
    memset (&utmpx_entry, 0, sizeof (utmpx_entry));

    /* Fill in the appropriate records of the utmpx entry.  */
    strncpy (utmpx_entry.ut_user, user_name, sizeof (utmpx_entry.ut_user));
    utmpx_entry.ut_pid = getppid ();

    id = hash_id (host_name);
    strncpy (utmpx_entry.ut_id, id, sizeof (utmpx_entry.ut_id));
    /* `who' expects that |ut_line| is null-terminated at utmp (!) size.  */
    strncpy (utmpx_entry.ut_line, host_name, sizeof (utmp_entry.ut_line) - 1);
    utmpx_entry.ut_type = dflag ? DEAD_PROCESS : USER_PROCESS;
    utmpx_entry.ut_syslen = min (strlen (host_name) + 1,
                                 sizeof (utmpx_entry.ut_host));
    strncpy (utmpx_entry.ut_host, host_name, utmpx_entry.ut_syslen);
    time (&utmpx_entry.ut_tv.tv_sec);

    /* It is not documented but |pututxline| modifies both utmp and utmpx. */
#ifdef DEBUG    
    utmpxname (UTMPX_FILE);
#endif
    setutxent ();
    getutxid (&utmpx_entry);
    if (!pututxline (&utmpx_entry))
        syslog (LOG_ERR, "%s: %m", UTMP_FILE);
    endutxent ();
    
    /* Append analogous entries to wtmp and wtmpx.  */
    updwtmpx (WTMPX_FILE, &utmpx_entry);

    /* Modify user's lastlog file.  */
    if (!dflag) {
        strcpy (llog_name, LASTLOG_DIR);
        strcat (llog_name, user_name);
	llog = open (llog_name, O_WRONLY | O_CREAT, 0644);
	if (llog != -1) {
	    struct lastlog ll;
            
	    memset ((char *) &ll, 0, sizeof (ll));
	    ll.ll_time = utmpx_entry.ut_tv.tv_sec;
            strncpy (ll.ll_host, host_name, sizeof (ll.ll_host));
            /* Using |write| tries to avoid race conditions (?).  */
	    if (write (llog, (char *) &ll, sizeof (ll)) != sizeof (ll))
                syslog (LOG_ERR, "%s: %m", llog_name);
	    close (llog);
	} else
            syslog (LOG_ERR, "%s: %m", llog_name);
    }
    return 0;
}
