#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <utmp.h>
#include <string.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include "pututmp_internal.h"

static const char *prog="pututmp";

static long read_long(const char *arg)
{
	char *end;
	long num;

	errno=0;
	num=strtol(arg, &end, 10);
	if (errno==ERANGE)
	{
		perror("strtol");
		exit(1);
	}
	if (!*arg || *end)
	{
		fprintf(stderr, "%s: Invalid number %s\n", prog, arg);
		exit(1);
	}
	return num;
}

static short read_short(const char *arg)
{
	long num;
	short s;

	num=read_long(arg);
	s=(short)num;
	if (s!=num) 
	{
		fprintf(stderr, "%s: Invalid short %s\n", prog, arg);
		exit(1);
	}
	return s;
}

static void read_string(char *dst, unsigned int len, const char *src)
{
	strncpy(dst, src, len);
}

static int read_addr_byte(const char **src, char term)
{
	char *end;
	unsigned long num;

	if (!*src) return 0;
	errno=0;
	num=strtoul(*src, &end, 10);
	if (errno==ERANGE)
	{
		perror("strtol");
		exit(1);
	}
	if (num>255 || !*src || *end!=term) 
	{
		*src=NULL;
		return 0;
	}
	*src=end+1;
	return num;
}

static void read_addr(void *dst, const char *src)
{
	char *dstc=(char *)dst;
	dstc[0]=read_addr_byte(&src, '.');
	dstc[1]=read_addr_byte(&src, '.');
	dstc[2]=read_addr_byte(&src, '.');
	dstc[3]=read_addr_byte(&src, 0);
	if (!src) 
	{
		fprintf(stderr, "%s: Invalid address %s\n", prog, src);
		exit(1);
	}
}

int main(int argc, char *argv[])
{
	char *slash;
	struct utmp ut;
	struct passwd *pw;
	char device[11]="/dev/tty";
	struct stat statbuf;
	uid_t uid;
	char ut_user[UT_NAMESIZE];
	time_t now;

	/* Check for right version/arguments */
	if (argc) prog=argv[0];
	if ((slash=strrchr(prog, '/'))) prog=slash+1;
	if (argc<2)
	{
		fprintf(stderr, "Usage: %s version args...\n", prog);
		return 1;
	}
	if (strcmp(argv[1], "1"))
	{
		fprintf(stderr, "%s: Version should be 1 (libc5-style utmp)\n",
			prog);
		return 1;
	}
	if (argc!=10)
	{
		fprintf(stderr, "Usage: %s 1 ut_type ut_pid ut_line "
			"ut_id ut_time ut_user ut_host addr\n", prog);
		return 1;
	}

	/* Read data from args into utmp structure */
	memset(&ut, 0, sizeof(ut));
	ut.ut_type=read_short(argv[2]);
	ut.ut_pid=read_short(argv[3]);
	read_string(ut.ut_line, sizeof(ut.ut_line), argv[4]);
	read_string(ut.ut_id, sizeof(ut.ut_id), argv[5]);
	ut.ut_time=read_long(argv[6]);
	read_string(ut.ut_user, sizeof(ut.ut_user), argv[7]);
	read_string(ut.ut_host, sizeof(ut.ut_host), argv[8]);
	read_addr(&ut.ut_addr, argv[9]);

	/* Check that arguments make sense */
	if (ut.ut_type!=USER_PROCESS && ut.ut_type!=DEAD_PROCESS)
	{
		fprintf(stderr, "%s: Only user and dead process are "
			"supported\n", prog);
		return 1;
	}
	if (ut.ut_type==USER_PROCESS && ut.ut_pid!=getppid())
	{
		fprintf(stderr, "%s: pid must match the calling process\n", 
			prog);
		return 1;
	}
	if (strncmp(ut.ut_line, "tty", 3)
		|| ut.ut_line[3]==0 || ut.ut_line[3]=='/' 
		|| ut.ut_line[4]==0 || ut.ut_line[4]=='/'
		|| ut.ut_line[5])
	{
		fprintf(stderr, "%s: line must be the name of a tty device\n", 
			prog);
		return 1;
	}
	strcat(device, ut.ut_line+3); /* Append 2 chars */
	if (stat(device, &statbuf))
	{
		perror("stat");
		return 1;
	}
	if (!S_ISCHR(statbuf.st_mode) || 
		MAJOR(statbuf.st_rdev)!=PTY_SLAVE_MAJOR)
	{
		fprintf(stderr, "%s: line must be a pseudo-tty slave\n", 
			prog);
		return 1;
	}
	uid=getuid();
	if (statbuf.st_uid!=uid)
	{
		fprintf(stderr, "%s: You must own the tty device\n", 
			prog);
		return 1;
	}
	if (ut.ut_id[0]!=ut.ut_line[3] || ut.ut_id[1]!=ut.ut_line[4])
	{
		fprintf(stderr, "%s: line and id must match\n", 
			prog);
		return 1;
	}
	if (!(pw=getpwuid(uid)))
	{
		fprintf(stderr, "%s: Can't find your entry in the password "
			"map\n", prog);
		return 1;
	}
	now=time(NULL);
	if (ut.ut_time>now)
	{
		fprintf(stderr, "%s: umtp entry in the future\n", prog);
		return 1;
	}
	if (now-ut.ut_time>60 && ut.ut_type==USER_PROCESS)
	{
		fprintf(stderr, "%s: umtp entry too old\n", prog);
		return 1;
	}
	strncpy(ut_user, pw->pw_name, sizeof(ut.ut_user));
	if (strncmp(ut_user, ut.ut_user, sizeof(ut.ut_user)))
	{
		fprintf(stderr, "%s: name doesn't correspond with your uid\n",
			prog);
		return 1;
	}
	/* Don't bother checking host and ipaddr - we have to take
           ipaddr on trust anyway, and the host is truncated */

	/* For dead processes, we get the old record and clear
           appropriate fields for programs that don't know about
           DEAD_PROCESS. */
	if (ut.ut_type==DEAD_PROCESS)
	{
		struct utmp *found;
		setutent();
		found=getutid(&ut);
		if (!found) return 1;
		memcpy(&ut, found, sizeof(ut));
		memset(ut.ut_line, 0, UT_LINESIZE);
		memset(ut.ut_user, 0, UT_NAMESIZE);
		ut.ut_type=DEAD_PROCESS;
		ut.ut_time=0;
	}
	setutent();
	pututline(&ut);
	endutent();
	return 0;
}
