#include <stdio.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utmpx.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <ctype.h>
#include <stropts.h>
#include <syslog.h>
#define UTMPX_NARGS 14
#define NORMAL_EXIT 0
#define BAD_ARGS 1
#define PUTUTXLINE_FAILURE 2
#define FORK_FAILURE 3
#define SETSID_FAILURE 4
#define ALREADY_DEAD 5
#define ENTRY_NOTFOUND 6
#define ILLEGAL_ARGUMENT 7
#define DEVICE_ERROR 8
#define MAX_SYSLEN 257
#define BUF_SIZE 256
#define ROOT_UID 0
#ifdef DEBUG
#define dprintf printf
#define dprintf3 printf
static void display_args();
#else
#define dprintf(x, y)
#define dprintf3(w, x, y, z)
#endif
static void load_utmpx_struct(struct utmpx *, char **);
static void usage(void);
static void check_utmpx(struct utmpx *);
static int bad_hostname(char *, int);
static int hex2bin(unsigned char);
static int invalid_utmpx(struct utmpx *, struct utmpx *);
static int bad_line(char *);
static void check_id(char *, char *);
int
main(int argc, char *argv[])
{
int devfd, err;
struct utmpx *rutmpx;
struct utmpx entryx;
struct stat stat_arg, stat_db;
#ifdef DEBUG
int debugger = 1;
printf("%d\n", getpid());
display_args(argc, argv);
#endif
if (argc != UTMPX_NARGS) {
usage();
return (BAD_ARGS);
}
if (getuid() == ROOT_UID) {
usage();
return (ILLEGAL_ARGUMENT);
}
load_utmpx_struct(&entryx, argv);
check_utmpx(&entryx);
if ((devfd = open("/dev", O_RDONLY)) < 0) {
usage();
return (DEVICE_ERROR);
}
if (fstatat(devfd, entryx.ut_line, &stat_arg, 0) < 0) {
(void) close(devfd);
usage();
return (DEVICE_ERROR);
}
err = 0;
for (rutmpx = getutxent(); rutmpx != (struct utmpx *)NULL;
rutmpx = getutxent()) {
if ((rutmpx->ut_type != USER_PROCESS) &&
(rutmpx->ut_type != DEAD_PROCESS))
continue;
if (fstatat(devfd, rutmpx->ut_line, &stat_db, 0) < 0)
continue;
if (stat_arg.st_ino == stat_db.st_ino &&
stat_arg.st_dev == stat_db.st_dev) {
if (rutmpx->ut_type == USER_PROCESS)
err = invalid_utmpx(&entryx, rutmpx);
break;
}
}
(void) close(devfd);
if (err) {
usage();
return (ILLEGAL_ARGUMENT);
}
if (pututxline(&entryx) == (struct utmpx *)NULL) {
return (PUTUTXLINE_FAILURE);
}
return (NORMAL_EXIT);
}
static int
hex2bin(unsigned char c)
{
if ('0' <= c && c <= '9')
return (c - '0');
else if ('A' <= c && c <= 'F')
return (10 + c - 'A');
else if ('a' <= c && c <= 'f')
return (10 + c - 'a');
dprintf("Bad hex character: 0x%x\n", c);
exit(ILLEGAL_ARGUMENT);
}
static void
load_utmpx_struct(struct utmpx *entryx, char *argv[])
{
char *user, *id, *line, *pid, *type, *term, *time_usec,
*exitstatus, *xtime, *session, *pad, *syslen, *host;
int temp, i;
unsigned char *cp;
(void) memset(entryx, 0, sizeof (struct utmpx));
user = argv[1];
id = argv[2];
line = argv[3];
pid = argv[4];
type = argv[5];
term = argv[6];
exitstatus = argv[7];
xtime = argv[8];
time_usec = argv[9 ];
session = argv[10];
pad = argv[11];
syslen = argv[12];
host = argv[13];
(void) strncpy(entryx->ut_user, user, sizeof (entryx->ut_user));
(void) strncpy(entryx->ut_id, id, sizeof (entryx->ut_id));
(void) strncpy(entryx->ut_line, line, sizeof (entryx->ut_line));
(void) sscanf(pid, "%d", &temp);
entryx->ut_pid = temp;
(void) sscanf(type, "%d", &temp);
entryx->ut_type = temp;
(void) sscanf(term, "%d", &temp);
entryx->ut_exit.e_termination = temp;
(void) sscanf(exitstatus, "%d", &temp);
entryx->ut_exit.e_exit = temp;
if (entryx->ut_type == USER_PROCESS)
setuserx(*entryx);
(void) sscanf(xtime, "%d", &temp);
entryx->ut_tv.tv_sec = temp;
(void) sscanf(time_usec, "%d", &temp);
entryx->ut_tv.tv_usec = temp;
(void) sscanf(session, "%d", &temp);
entryx->ut_session = temp;
temp = strlen(pad);
cp = (unsigned char *)entryx->pad;
for (i = 0; i < temp && (i>>1) < sizeof (entryx->pad); i += 2)
cp[i>>1] = hex2bin(pad[i]) << 4 | hex2bin(pad[i+1]);
(void) sscanf(syslen, "%d", &temp);
entryx->ut_syslen = temp;
(void) strlcpy(entryx->ut_host, host, sizeof (entryx->ut_host));
}
static void
usage()
{
syslog(LOG_ERR, "Wrong number of arguments or invalid user \n");
}
static void
check_utmpx(struct utmpx *entryx)
{
char buf[BUF_SIZE];
char *line = buf;
struct passwd *pwd;
int uid;
int hostlen;
char *user;
uid_t ruid = getuid();
(void) memset(buf, 0, BUF_SIZE);
user = malloc(sizeof (entryx->ut_user) +1);
(void) strncpy(user, entryx->ut_user, sizeof (entryx->ut_user));
user[sizeof (entryx->ut_user)] = '\0';
pwd = getpwnam(user);
(void) free(user);
(void) strlcat(strcpy(buf, "/dev/"), entryx->ut_line, sizeof (buf));
if (pwd != (struct passwd *)NULL) {
uid = pwd->pw_uid;
if (ruid != uid) {
dprintf3("Bad uid: user %s = %d uid = %d \n",
entryx->ut_user, uid, getuid());
exit(ILLEGAL_ARGUMENT);
}
} else if (entryx->ut_type != DEAD_PROCESS) {
dprintf("Bad user name: %s \n", entryx->ut_user);
exit(ILLEGAL_ARGUMENT);
}
if (!(entryx->ut_type == USER_PROCESS ||
entryx->ut_type == DEAD_PROCESS)) {
dprintf("Bad type type = %d\n", entryx->ut_type);
exit(ILLEGAL_ARGUMENT);
}
#ifdef VERIFY_PID
if (entryx->ut_type == USER_PROCESS && entryx->ut_pid != getppid()) {
dprintf("Bad pid = %d\n", entryx->ut_pid);
exit(ILLEGAL_ARGUMENT);
}
#endif
if (bad_line(line) == 1) {
dprintf("Bad line = %s\n", line);
exit(ILLEGAL_ARGUMENT);
}
hostlen = strlen(entryx->ut_host) + 1;
if (entryx->ut_syslen != hostlen) {
dprintf3("Bad syslen of \"%s\" = %d - correcting to %d\n",
entryx->ut_host, entryx->ut_syslen, hostlen);
entryx->ut_syslen = hostlen;
}
if (bad_hostname(entryx->ut_host, entryx->ut_syslen) == 1) {
dprintf("Bad hostname name = %s\n", entryx->ut_host);
exit(ILLEGAL_ARGUMENT);
}
check_id(entryx->ut_id, entryx->ut_line);
}
static int
bad_hostname(char *name, int len)
{
int i;
if (len < 0 || len > MAX_SYSLEN)
return (1);
for (i = 0; i < len; i++)
if (name[i] != '\0' && isprint(name[i]) == 0)
name[i] = ' ';
return (0);
}
static void
check_id(char *id, char *line)
{
int i, len;
if (id[1] == '/' && id[2] == 's' && id[3] == 't') {
len = strlen(line);
if (len > 0)
len--;
for (i = 0; i < 4; i++)
id[i] = len - i < 0 ? 0 : line[len-i];
}
}
static int
invalid_utmpx(struct utmpx *eutmpx, struct utmpx *rutmpx)
{
#define SUTMPX_ID (sizeof (eutmpx->ut_id))
#define SUTMPX_USER (sizeof (eutmpx->ut_user))
return (!nonuserx(*rutmpx) ||
strncmp(eutmpx->ut_id, rutmpx->ut_id, SUTMPX_ID) != 0 ||
strncmp(eutmpx->ut_user, rutmpx->ut_user, SUTMPX_USER) != 0);
}
static int
bad_line(char *line)
{
struct stat statbuf;
int fd;
if (strstr(line, "../") != 0) {
dprintf("Bad line = %s\n", line);
return (1);
}
if (seteuid(getuid()) != 0)
return (1);
if ((fd = open(line, O_WRONLY|O_NOCTTY|O_NONBLOCK)) == -1) {
dprintf("Bad line (Can't open/write) = %s\n", line);
return (1);
}
if (isatty(fd) == 1) {
(void) close(fd);
if (seteuid(ROOT_UID) != 0)
return (1);
return (0);
}
if ((fstat(fd, &statbuf) < 0) || !S_ISCHR(statbuf.st_mode)) {
dprintf("Bad line (fstat failed) (Not S_IFCHR) = %s\n", line);
(void) close(fd);
return (1);
}
if (isastream(fd) != 1) {
dprintf("Bad line (isastream failed) = %s\n", line);
(void) close(fd);
return (1);
}
if (ioctl(fd, I_PUSH, "ptem") == -1) {
dprintf("Bad line (I_PUSH of \"ptem\" failed) = %s\n", line);
(void) close(fd);
return (1);
}
if (isatty(fd) != 1) {
dprintf("Bad line (isatty failed) = %s\n", line);
(void) close(fd);
return (1);
}
if (ioctl(fd, I_POP, 0) == -1) {
dprintf("Bad line (I_POP of \"ptem\" failed) = %s\n", line);
(void) close(fd);
return (1);
}
(void) close(fd);
if (seteuid(ROOT_UID) != 0)
return (1);
return (0);
}
#ifdef DEBUG
static void
display_args(argc, argv)
int argc;
char **argv;
{
int i = 0;
while (argc--) {
printf("Argument #%d = %s\n", i, argv[i]);
i++;
}
}
fputmpx(struct utmpx *rutmpx)
{
printf("ut_user = \"%-32.32s\" \n", rutmpx->ut_user);
printf("ut_id = \"%-4.4s\" \n", rutmpx->ut_id);
printf("ut_line = \"%-32.32s\" \n", rutmpx->ut_line);
printf("ut_pid = \"%d\" \n", rutmpx->ut_pid);
printf("ut_type = \"%d\" \n", rutmpx->ut_type);
printf("ut_exit.e_termination = \"%d\" \n",
rutmpx->ut_exit.e_termination);
printf("ut_exit.e_exit = \"%d\" \n", rutmpx->ut_exit.e_exit);
}
#endif