getutx.c revision 92ba710950bf6ee35de67e1e0b7f20ec9e528106
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Routines to read and write the /etc/utmpx file. Also contains
* binary compatibility routines to support the old utmp interfaces
* on systems with MAXPID <= SHRT_MAX.
*/
#pragma weak getutxent = _getutxent
#pragma weak getutent = _getutent
#pragma weak getutxid = _getutxid
#pragma weak getutid = _getutid
#pragma weak getutxline = _getutxline
#pragma weak getutline = _getutline
#pragma weak getutmpx = _getutmpx
#pragma weak getutmp = _getutmp
#pragma weak makeutx = _makeutx
#pragma weak makeut = _makeut
#pragma weak modutx = _modutx
#pragma weak modut = _modut
#pragma weak pututxline = _pututxline
#pragma weak pututline = _pututline
#pragma weak setutxent = _setutxent
#pragma weak setutent = _setutent
#pragma weak endutxent = _endutxent
#pragma weak endutent = _endutent
#pragma weak utmpxname = _utmpxname
#pragma weak utmpname = _utmpname
#pragma weak updwtmpx = _updwtmpx
#pragma weak updwtmp = _updwtmp
#include "synonyms.h"
#include <sys/types.h>
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <utmpx.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <limits.h>
#include <signal.h>
#include <spawn.h>
#define IDLEN 4 /* length of id field in utmp */
#define SC_WILDC 0xff /* wild char for utmp ids */
#define MAXFILE 79 /* Maximum pathname length for "utmpx" file */
#define MAXVAL 255 /* max value for an id `character' */
#define IPIPE "/var/run/initpipe" /* FIFO to send pids to init */
#define UPIPE "/var/run/utmppipe" /* FIFO to send pids to utmpd */
#define VAR_UTMPX_FILE "/var/adm/utmpx" /* for sanity check only */
/*
* format of message sent to init
*/
typedef struct pidrec {
int pd_type; /* command type */
pid_t pd_pid; /* pid */
} pidrec_t;
/*
* pd_type's
*/
#define ADDPID 1 /* add a pid to "godchild" list */
#define REMPID 2 /* remove a pid to "godchild" list */
static void utmpx_frec2api(const struct futmpx *, struct utmpx *);
static void utmpx_api2frec(const struct utmpx *, struct futmpx *);
static void unlockutx(void);
static void sendpid(int, pid_t);
static void sendupid(int, pid_t);
static int idcmp(const char *, const char *);
static int allocid(char *, unsigned char *);
static int lockutx(void);
static struct utmpx *invoke_utmp_update(const struct utmpx *);
static struct futmpx *getoneutx(off_t *);
static void putoneutx(const struct utmpx *, off_t);
static int big_pids_in_use(void);
/*
* prototypes for utmp compatibility routines (in getut.c)
*/
extern struct utmp *_compat_getutent(void);
extern struct utmp *_compat_getutid(const struct utmp *);
extern struct utmp *_compat_getutline(const struct utmp *);
extern struct utmp *_compat_pututline(const struct utmp *);
extern void _compat_setutent(void);
extern void _compat_endutent(void);
extern void _compat_updwtmp(const char *, struct utmp *);
extern struct utmp *_compat_makeut(struct utmp *);
static int fd = -1; /* File descriptor for the utmpx file. */
static int ut_got_maxpid = 0; /* Flag set when sysconf(_SC_MAXPID) called */
static pid_t ut_maxpid = 0; /* Value of MAXPID from sysconf */
static int tempfd = -1; /* To store fd between lockutx() and unlockutx() */
static FILE *fp = NULL; /* Buffered file descriptior for utmpx file */
static int changed_name = 0; /* Flag set when not using utmpx file */
static char utmpxfile[MAXFILE+1] = UTMPX_FILE; /* Name of the current */
char _compat_utmpfile[MAXFILE+1];
static int compat_utmpflag = 0; /* old compat mode flag */
static struct futmpx fubuf; /* Copy of last entry read in. */
static struct utmpx ubuf; /* Last entry returned to client */
static struct utmp utmpcompat; /* Buffer for returning utmp-format data */
/*
* In the 64-bit world, the utmpx data structure grows because of
* the ut_time field (a struct timeval) grows in the middle of it.
*/
static void
utmpx_frec2api(const struct futmpx *src, struct utmpx *dst)
{
if (src == NULL)
return;
(void) strncpy(dst->ut_user, src->ut_user, sizeof (dst->ut_user));
(void) strncpy(dst->ut_line, src->ut_line, sizeof (dst->ut_line));
(void) memcpy(dst->ut_id, src->ut_id, sizeof (dst->ut_id));
dst->ut_pid = src->ut_pid;
dst->ut_type = src->ut_type;
dst->ut_exit.e_termination = src->ut_exit.e_termination;
dst->ut_exit.e_exit = src->ut_exit.e_exit;
dst->ut_tv.tv_sec = (time_t)src->ut_tv.tv_sec;
dst->ut_tv.tv_usec = (suseconds_t)src->ut_tv.tv_usec;
dst->ut_session = src->ut_session;
bzero(dst->pad, sizeof (dst->pad));
dst->ut_syslen = src->ut_syslen;
(void) memcpy(dst->ut_host, src->ut_host, sizeof (dst->ut_host));
}
static void
utmpx_api2frec(const struct utmpx *src, struct futmpx *dst)
{
if (src == NULL)
return;
(void) strncpy(dst->ut_user, src->ut_user, sizeof (dst->ut_user));
(void) strncpy(dst->ut_line, src->ut_line, sizeof (dst->ut_line));
(void) memcpy(dst->ut_id, src->ut_id, sizeof (dst->ut_id));
dst->ut_pid = src->ut_pid;
dst->ut_type = src->ut_type;
dst->ut_exit.e_termination = src->ut_exit.e_termination;
dst->ut_exit.e_exit = src->ut_exit.e_exit;
dst->ut_tv.tv_sec = (time32_t)src->ut_tv.tv_sec;
dst->ut_tv.tv_usec = (int32_t)src->ut_tv.tv_usec;
dst->ut_session = src->ut_session;
bzero(dst->pad, sizeof (dst->pad));
dst->ut_syslen = src->ut_syslen;
(void) memcpy(dst->ut_host, src->ut_host, sizeof (dst->ut_host));
}
/*
* "getutxent_frec" gets the raw version of the next entry in the utmpx file.
*/
static struct futmpx *
getutxent_frec(void)
{
/*
* If the "utmpx" file is not open, attempt to open it for
* reading. If there is no file, attempt to create one. If
* both attempts fail, return NULL. If the file exists, but
* isn't readable and writeable, do not attempt to create.
*/
if (fd < 0) {
if ((fd = open(utmpxfile, O_RDWR|O_CREAT, 0644)) < 0) {
/*
* If the open failed for permissions, try opening
* it only for reading. All "pututxline()" later
* will fail the writes.
*/
if ((fd = open(utmpxfile, O_RDONLY)) < 0)
return (NULL);
if ((fp = fopen(utmpxfile, "rF")) == NULL) {
(void) close(fd);
fd = -1;
return (NULL);
}
} else {
/*
* Get the stream pointer
*/
if ((fp = fopen(utmpxfile, "r+F")) == NULL) {
(void) close(fd);
fd = -1;
return (NULL);
}
}
}
/*
* Try to read in the next entry from the utmpx file.
*/
if (fread(&fubuf, sizeof (fubuf), 1, fp) != 1) {
/*
* Make sure fubuf is zeroed.
*/
bzero(&fubuf, sizeof (fubuf));
return (NULL);
}
return (&fubuf);
}
/*
* "big_pids_in_use" determines whether large pid numbers are in use
* or not. If MAXPID won't fit in a signed short, the utmp.ut_pid
* field will overflow.
*
* Returns 0 if small pids are in use, 1 otherwise
*/
static int
big_pids_in_use(void)
{
if (!ut_got_maxpid) {
ut_got_maxpid++;
ut_maxpid = sysconf(_SC_MAXPID);
}
return (ut_maxpid > SHRT_MAX ? 1 : 0);
}
/*
* "getutxent" gets the next entry in the utmpx file.
*/
struct utmpx *
getutxent(void)
{
struct futmpx *futxp;
futxp = getutxent_frec();
utmpx_frec2api(&fubuf, &ubuf);
if (futxp == NULL)
return (NULL);
return (&ubuf);
}
/*
* "getutent" gets the next entry in the utmp file.
*/
struct utmp *
getutent(void)
{
struct utmpx *utmpx;
if (compat_utmpflag)
return (_compat_getutent());
/* fail if we can't represent maxpid properly */
if (big_pids_in_use()) {
errno = EOVERFLOW;
return (NULL);
}
if ((utmpx = getutxent()) == NULL)
return (NULL);
getutmp(utmpx, &utmpcompat);
return (&utmpcompat);
}
/*
* "getutxid" finds the specified entry in the utmpx file. If
* it can't find it, it returns NULL.
*/
struct utmpx *
getutxid(const struct utmpx *entry)
{
short type;
/*
* From XPG5: "The getutxid() or getutxline() may cache data.
* For this reason, to use getutxline() to search for multiple
* occurrences, it is necessary to zero out the static data after
* each success, or getutxline() could just return a pointer to
* the same utmpx structure over and over again."
*/
utmpx_api2frec(&ubuf, &fubuf);
/*
* Start looking for entry. Look in our current buffer before
* reading in new entries.
*/
do {
/*
* If there is no entry in "fubuf", skip to the read.
*/
if (fubuf.ut_type != EMPTY) {
switch (entry->ut_type) {
/*
* Do not look for an entry if the user sent
* us an EMPTY entry.
*/
case EMPTY:
return (NULL);
/*
* For RUN_LVL, BOOT_TIME, OLD_TIME, and NEW_TIME
* entries, only the types have to match. If they do,
* return the address of internal buffer.
*/
case RUN_LVL:
case BOOT_TIME:
case DOWN_TIME:
case OLD_TIME:
case NEW_TIME:
if (entry->ut_type == fubuf.ut_type) {
utmpx_frec2api(&fubuf, &ubuf);
return (&ubuf);
}
break;
/*
* For INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS,
* and DEAD_PROCESS the type of the entry in "fubuf",
* must be one of the above and id's must match.
*/
case INIT_PROCESS:
case LOGIN_PROCESS:
case USER_PROCESS:
case DEAD_PROCESS:
if (((type = fubuf.ut_type) == INIT_PROCESS ||
type == LOGIN_PROCESS ||
type == USER_PROCESS ||
type == DEAD_PROCESS) &&
(fubuf.ut_id[0] == entry->ut_id[0]) &&
(fubuf.ut_id[1] == entry->ut_id[1]) &&
(fubuf.ut_id[2] == entry->ut_id[2]) &&
(fubuf.ut_id[3] == entry->ut_id[3])) {
utmpx_frec2api(&fubuf, &ubuf);
return (&ubuf);
}
break;
/*
* Do not search for illegal types of entry.
*/
default:
return (NULL);
}
}
} while (getutxent_frec() != NULL);
/*
* Return NULL since the proper entry wasn't found.
*/
utmpx_frec2api(&fubuf, &ubuf);
return (NULL);
}
/*
* "getutid" finds the specified entry in the utmp file. If
* it can't find it, it returns NULL.
*/
struct utmp *
getutid(const struct utmp *entry)
{
struct utmpx utmpx;
struct utmpx *utmpx2;
if (compat_utmpflag)
return (_compat_getutid(entry));
/* fail if we can't represent maxpid properly */
if (big_pids_in_use()) {
errno = EOVERFLOW;
return (NULL);
}
getutmpx(entry, &utmpx);
if ((utmpx2 = getutxid(&utmpx)) == NULL)
return (NULL);
getutmp(utmpx2, &utmpcompat);
return (&utmpcompat);
}
/*
* "getutxline" searches the "utmpx" file for a LOGIN_PROCESS or
* USER_PROCESS with the same "line" as the specified "entry".
*/
struct utmpx *
getutxline(const struct utmpx *entry)
{
/*
* From XPG5: "The getutxid() or getutxline() may cache data.
* For this reason, to use getutxline() to search for multiple
* occurrences, it is necessary to zero out the static data after
* each success, or getutxline() could just return a pointer to
* the same utmpx structure over and over again."
*/
utmpx_api2frec(&ubuf, &fubuf);
do {
/*
* If the current entry is the one we are interested in,
* return a pointer to it.
*/
if (fubuf.ut_type != EMPTY &&
(fubuf.ut_type == LOGIN_PROCESS ||
fubuf.ut_type == USER_PROCESS) &&
strncmp(&entry->ut_line[0], &fubuf.ut_line[0],
sizeof (fubuf.ut_line)) == 0) {
utmpx_frec2api(&fubuf, &ubuf);
return (&ubuf);
}
} while (getutxent_frec() != NULL);
/*
* Since entry wasn't found, return NULL.
*/
utmpx_frec2api(&fubuf, &ubuf);
return (NULL);
}
/*
* "getutline" searches the "utmp" file for a LOGIN_PROCESS or
* USER_PROCESS with the same "line" as the specified "entry".
*/
struct utmp *
getutline(const struct utmp *entry)
{
struct utmpx utmpx;
struct utmpx *utmpx2;
if (compat_utmpflag)
return (_compat_getutline(entry));
/* fail if we can't represent maxpid properly */
if (big_pids_in_use()) {
errno = EOVERFLOW;
return (NULL);
}
/* call getutxline */
getutmpx(entry, &utmpx);
if ((utmpx2 = getutxline(&utmpx)) == NULL)
return (NULL);
getutmp(utmpx2, &utmpcompat);
return (&utmpcompat);
}
/*
* invoke_utmp_update
*
* Invokes the utmp_update program which has the privilege to write
* to the /etc/utmp file.
*/
#define UTMP_UPDATE "/usr/lib/utmp_update"
#define STRSZ 64 /* Size of char buffer for argument strings */
static struct utmpx *
invoke_utmp_update(const struct utmpx *entryx)
{
extern char **environ;
posix_spawnattr_t attr;
int status;
pid_t child;
pid_t w;
int i;
char user[STRSZ], id[STRSZ], line[STRSZ], pid[STRSZ], type[STRSZ],
term[STRSZ], exit[STRSZ], time[STRSZ], time_usec[STRSZ],
session_id[STRSZ], syslen[32];
char pad[sizeof (entryx->pad) * 2 + 1];
char host[sizeof (entryx->ut_host) + 1];
struct utmpx *curx = NULL;
char bin2hex[] = "0123456789ABCDEF";
unsigned char *cp;
char *argvec[15];
int error;
/*
* Convert the utmp struct to strings for command line arguments.
*/
(void) strncpy(user, entryx->ut_user, sizeof (entryx->ut_user));
user[sizeof (entryx->ut_user)] = '\0';
(void) strncpy(id, entryx->ut_id, sizeof (entryx->ut_id));
id[sizeof (entryx->ut_id)] = '\0';
(void) strncpy(line, entryx->ut_line, sizeof (entryx->ut_line));
line[sizeof (entryx->ut_line)] = '\0';
(void) sprintf(pid, "%d", entryx->ut_pid);
(void) sprintf(type, "%d", entryx->ut_type);
(void) sprintf(term, "%d", entryx->ut_exit.e_termination);
(void) sprintf(exit, "%d", entryx->ut_exit.e_exit);
(void) sprintf(time, "%ld", entryx->ut_tv.tv_sec);
(void) sprintf(time_usec, "%ld", entryx->ut_tv.tv_usec);
(void) sprintf(session_id, "%d", entryx->ut_session);
cp = (unsigned char *)entryx->pad;
for (i = 0; i < sizeof (entryx->pad); ++i) {
pad[i << 1] = bin2hex[(cp[i] >> 4) & 0xF];
pad[(i << 1) + 1] = bin2hex[cp[i] & 0xF];
}
pad[sizeof (pad) - 1] = '\0';
(void) sprintf(syslen, "%d", entryx->ut_syslen);
(void) strlcpy(host, entryx->ut_host, sizeof (host));
argvec[0] = UTMP_UPDATE;
argvec[1] = user;
argvec[2] = id;
argvec[3] = line;
argvec[4] = pid;
argvec[5] = type;
argvec[6] = term;
argvec[7] = exit;
argvec[8] = time;
argvec[9] = time_usec;
argvec[10] = session_id;
argvec[11] = pad;
argvec[12] = syslen;
argvec[13] = host;
argvec[14] = NULL;
/*
* No SIGCHLD, please, and let no one else reap our child.
*/
error = posix_spawnattr_init(&attr);
if (error) {
errno = error;
goto out;
}
error = posix_spawnattr_setflags(&attr,
POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP);
if (error) {
(void) posix_spawnattr_destroy(&attr);
errno = error;
goto out;
}
error = posix_spawn(&child, UTMP_UPDATE, NULL, &attr, argvec, environ);
(void) posix_spawnattr_destroy(&attr);
if (error) {
errno = error;
goto out;
}
do {
w = waitpid(child, &status, 0);
} while (w == -1 && errno == EINTR);
/*
* We can get ECHILD if the process is ignoring SIGCLD.
*/
if (!(w == -1 && errno == ECHILD) &&
(w == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)) {
/*
* The child encountered an error,
*/
goto out;
}
/*
* Normal termination. Return a pointer to the entry we just made.
*/
setutxent(); /* Reset file pointer */
while ((curx = getutxent()) != NULL) {
if (curx->ut_type != EMPTY &&
(curx->ut_type == LOGIN_PROCESS ||
curx->ut_type == USER_PROCESS ||
curx->ut_type == DEAD_PROCESS) &&
strncmp(&entryx->ut_line[0], &curx->ut_line[0],
sizeof (curx->ut_line)) == 0)
break;
}
out:
return (curx);
}
/*
* "pututxline" writes the structure sent into the utmpx file.
* If there is already an entry with the same id, then it is
* overwritten, otherwise a new entry is made at the end of the
* utmpx file.
*/
struct utmpx *
pututxline(const struct utmpx *entry)
{
struct utmpx *answer;
int lock = 0;
struct utmpx tmpxbuf;
struct futmpx ftmpxbuf;
/*
* Copy the user supplied entry into our temporary buffer to
* avoid the possibility that the user is actually passing us
* the address of "ubuf".
*/
if (entry == NULL)
return (NULL);
(void) memcpy(&tmpxbuf, entry, sizeof (tmpxbuf));
utmpx_api2frec(entry, &ftmpxbuf);
if (fd < 0) {
(void) getutxent_frec();
if (fd < 0)
return ((struct utmpx *)NULL);
}
/*
* If we are not the superuser than we can't write to /etc/utmp,
* so invoke update_utmp(8) to write the entry for us.
*/
if (changed_name == 0 && geteuid() != 0)
return (invoke_utmp_update(entry));
/*
* Find the proper entry in the utmpx file. Start at the current
* location. If it isn't found from here to the end of the
* file, then reset to the beginning of the file and try again.
* If it still isn't found, then write a new entry at the end of
* the file. (Making sure the location is an integral number of
* utmp structures into the file incase the file is scribbled.)
*/
if (getutxid(&tmpxbuf) == NULL) {
setutxent();
/*
* Lock the the entire file from here onwards.
*/
if (getutxid(&tmpxbuf) == NULL) {
lock++;
if (lockf(fd, F_LOCK, 0) < NULL)
return (NULL);
(void) fseek(fp, 0, SEEK_END);
} else
(void) fseek(fp, -(long)sizeof (struct futmpx),
SEEK_CUR);
} else
(void) fseek(fp, -(long)sizeof (struct futmpx), SEEK_CUR);
/*
* Write out the user supplied structure. If the write fails,
* then the user probably doesn't have permission to write the
* utmpx file.
*/
if (fwrite(&ftmpxbuf, sizeof (ftmpxbuf), 1, fp) != 1) {
answer = (struct utmpx *)NULL;
} else {
/*
* Save the new user structure into ubuf and fubuf so that
* it will be up to date in the future.
*/
(void) fflush(fp);
(void) memcpy(&fubuf, &ftmpxbuf, sizeof (fubuf));
utmpx_frec2api(&fubuf, &ubuf);
answer = &ubuf;
}
if (lock)
(void) lockf(fd, F_ULOCK, 0);
if (answer != NULL && (tmpxbuf.ut_type == USER_PROCESS ||
tmpxbuf.ut_type == DEAD_PROCESS))
sendupid(tmpxbuf.ut_type == USER_PROCESS ? ADDPID : REMPID,
(pid_t)tmpxbuf.ut_pid);
return (answer);
}
/*
* "pututline" is a wrapper that calls pututxline after converting
* the utmp record to a utmpx record.
*/
struct utmp *
pututline(const struct utmp *entry)
{
struct utmpx utmpx;
struct utmpx *utmpx2;
if (compat_utmpflag)
return (_compat_pututline(entry));
getutmpx(entry, &utmpx);
if ((utmpx2 = pututxline(&utmpx)) == NULL)
return (NULL);
getutmp(utmpx2, &utmpcompat);
return (&utmpcompat);
}
/*
* "setutxent" just resets the utmpx file back to the beginning.
*/
void
setutxent(void)
{
if (fd != -1)
(void) lseek(fd, 0L, SEEK_SET);
if (fp != NULL)
(void) fseek(fp, 0L, SEEK_SET);
/*
* Zero the stored copy of the last entry read, since we are
* resetting to the beginning of the file.
*/
bzero(&ubuf, sizeof (ubuf));
bzero(&fubuf, sizeof (fubuf));
}
/*
* "setutent" is a wrapper that calls setutxent
*/
void
setutent(void)
{
if (compat_utmpflag) {
_compat_setutent();
return;
}
setutxent();
}
/*
* "endutxent" closes the utmpx file.
*/
void
endutxent(void)
{
if (fd != -1)
(void) close(fd);
fd = -1;
if (fp != NULL)
(void) fclose(fp);
fp = NULL;
bzero(&ubuf, sizeof (ubuf));
bzero(&fubuf, sizeof (fubuf));
}
/*
* "endutent" is a wrapper that calls endutxent
* and clears the utmp compatibility buffer.
*/
void
endutent(void)
{
if (compat_utmpflag) {
_compat_endutent();
return;
}
endutxent();
bzero(&utmpcompat, sizeof (utmpcompat));
}
/*
* "utmpxname" allows the user to read a file other than the
* normal "utmpx" file.
*/
int
utmpxname(const char *newfile)
{
size_t len;
/*
* Determine if the new filename will fit. If not, return 0.
*/
if ((len = strlen(newfile)) > MAXFILE-1)
return (0);
/*
* The name of the utmpx file has to end with 'x'
*/
if (newfile[len-1] != 'x')
return (0);
/*
* Otherwise copy in the new file name.
*/
else
(void) strcpy(&utmpxfile[0], newfile);
/*
* Make sure everything is reset to the beginning state.
*/
endutxent();
/*
* If the file is being changed to /etc/utmpx or /var/adm/utmpx then
* we clear the flag so pututxline invokes utmp_update. Otherwise
* we set the flag indicating that they changed to another name.
*/
if (strcmp(utmpxfile, UTMPX_FILE) == 0 ||
strcmp(utmpxfile, VAR_UTMPX_FILE) == 0)
changed_name = 0;
else
changed_name = 1;
return (1);
}
/*
* "utmpname" allows the user to read a file other than the
* normal "utmp" file. If the file specified is "/var/adm/utmp"
* or "/var/adm/wtmp", it is translated to the corresponding "utmpx"
* format name, and all "utmp" operations become wrapped calls
* to the equivalent "utmpx" routines, with data conversions
* as appropriate. In the event the application wishes to read
* an actual "old" utmp file (named something other than /var/adm/utmp),
* calling this function with that name enables backward compatibility
* mode, where we actually call the old utmp routines to operate on
* the old file.
*/
int
utmpname(const char *newfile)
{
char name[MAXFILE+1];
if (strlen(newfile) > MAXFILE)
return (0);
if (strcmp(newfile, "/var/adm/utmp") == 0 ||
strcmp(newfile, "/var/adm/wtmp") == 0) {
(void) strcpy(name, newfile);
(void) strcat(name, "x");
compat_utmpflag = 0; /* turn off old compat mode */
return (utmpxname(name));
} else {
(void) strcpy(_compat_utmpfile, newfile);
compat_utmpflag = 1;
return (1);
}
}
/*
* Add the record to wtmpx.
*/
void
updwtmpx(const char *filex, struct utmpx *utx)
{
struct futmpx futx;
int wfdx;
if ((wfdx = open(filex, O_WRONLY | O_APPEND)) < 0)
return;
(void) lseek(wfdx, 0, SEEK_END);
utmpx_api2frec(utx, &futx);
(void) write(wfdx, &futx, sizeof (futx));
done:
(void) close(wfdx);
}
/*
* Add record to wtmp (actually wtmpx). If not updating /var/adm/wtmp,
* use the old utmp compatibility routine to write a utmp-format
* record to the file specified.
*/
void
updwtmp(const char *file, struct utmp *ut)
{
struct utmpx utmpx;
char xfile[MAXFILE + 1];
if (strcmp(file, "/var/adm/wtmp") == 0) {
(void) strlcpy(xfile, file, sizeof (xfile) - 1);
(void) strcat(xfile, "x");
getutmpx(ut, &utmpx);
updwtmpx((const char *)&xfile, &utmpx);
} else
_compat_updwtmp(file, ut);
}
/*
* modutx - modify a utmpx entry. Also notify init about new pids or
* old pids that it no longer needs to care about
*
* args: utp- point to utmpx structure to be created
*/
struct utmpx *
modutx(const struct utmpx *utp)
{
int i;
struct utmpx utmp; /* holding area */
struct utmpx *ucp = &utmp; /* and a pointer to it */
struct utmpx *up; /* "current" utmpx entry */
struct futmpx *fup; /* being examined */
for (i = 0; i < IDLEN; ++i) {
if ((unsigned char)utp->ut_id[i] == SC_WILDC)
return (NULL);
}
/*
* copy the supplied utmpx structure someplace safe
*/
(void) memcpy(&utmp, utp, sizeof (utmp));
setutxent();
while (fup = getutxent_frec()) {
if (idcmp(ucp->ut_id, fup->ut_id))
continue;
/*
* only get here if ids are the same, i.e. found right entry
*/
if (ucp->ut_pid != fup->ut_pid) {
sendpid(REMPID, (pid_t)fup->ut_pid);
sendpid(ADDPID, (pid_t)ucp->ut_pid);
}
break;
}
up = pututxline(ucp);
if (ucp->ut_type == DEAD_PROCESS)
sendpid(REMPID, (pid_t)ucp->ut_pid);
if (up)
updwtmpx(WTMPX_FILE, up);
endutxent();
return (up);
}
/*
* modut - modify a utmp entry. Also notify init about new pids or
* old pids that it no longer needs to care about
*
* args: utmp - point to utmp structure to be created
*/
struct utmp *
modut(struct utmp *utp)
{
struct utmpx utmpx;
struct utmpx *utmpx2;
getutmpx(utp, &utmpx);
if ((utmpx2 = modutx(&utmpx)) == NULL)
return (NULL);
getutmp(utmpx2, utp);
return (utp);
}
/*
* idcmp - compare two id strings, return 0 if same, non-zero if not *
* args: s1 - first id string
* s2 - second id string
*/
static int
idcmp(const char *s1, const char *s2)
{
int i;
for (i = 0; i < IDLEN; ++i)
if ((unsigned char) *s1 != SC_WILDC && (*s1++ != *s2++))
return (-1);
return (0);
}
/*
* allocid - allocate an unused id for utmp, either by recycling a
* DEAD_PROCESS entry or creating a new one. This routine only
* gets called if a wild card character was specified.
*
* args: srcid - pattern for new id
* saveid - last id matching pattern for a non-dead process
*/
static int
allocid(char *srcid, unsigned char *saveid)
{
int i; /* scratch variable */
int changed; /* flag to indicate that a new id has */
/* been generated */
char copyid[IDLEN]; /* work area */
(void) memcpy(copyid, srcid, IDLEN);
changed = 0;
for (i = 0; i < IDLEN; ++i) {
/*
* if this character isn't wild, it'll be part of the
* generated id
*/
if ((unsigned char) copyid[i] != SC_WILDC)
continue;
/*
* it's a wild character, retrieve the character from the
* saved id
*/
copyid[i] = saveid[i];
/*
* if we haven't changed anything yet, try to find a new char
* to use
*/
if (!changed && (saveid[i] < MAXVAL)) {
/*
* Note: this algorithm is taking the "last matched" id
* and trying to make a 1 character change to it to create
* a new one. Rather than special-case the first time
* (when no perturbation is really necessary), just don't
* allocate the first valid id.
*/
while (++saveid[i] < MAXVAL) {
/*
* make sure new char is alphanumeric
*/
if (isalnum(saveid[i])) {
copyid[i] = saveid[i];
changed = 1;
break;
}
}
if (!changed) {
/*
* Then 'reset' the current count at
* this position to it's lowest valid
* value, and propagate the carry to
* the next wild-card slot
*
* See 1113208.
*/
saveid[i] = 0;
while (!isalnum(saveid[i]))
saveid[i]++;
copyid[i] = ++saveid[i];
}
}
}
/*
* changed is true if we were successful in allocating an id
*/
if (changed) {
(void) memcpy(srcid, copyid, IDLEN);
return (0);
} else {
return (-1);
}
}
/*
* lockutx - lock utmpx file
*/
static int
lockutx(void)
{
int lockfd;
if ((lockfd = open(UTMPX_FILE, O_RDWR|O_CREAT, 0644)) < 0)
return (-1);
if (lockf(lockfd, F_LOCK, 0) < 0) {
(void) close(lockfd);
return (-1);
}
tempfd = fd;
fd = lockfd;
return (0);
}
/*
* unlockutx - unlock utmpx file
*/
static void
unlockutx(void)
{
(void) lockf(fd, F_ULOCK, 0);
(void) close(fd);
fd = tempfd;
}
/*
* sendpid - send message to init to add or remove a pid from the
* "godchild" list
*
* args: cmd - ADDPID or REMPID
* pid - pid of "godchild"
*/
static void
sendpid(int cmd, pid_t pid)
{
int pfd; /* file desc. for init pipe */
pidrec_t prec; /* place for message to be built */
/*
* if for some reason init didn't open initpipe, open it read/write
* here to avoid sending SIGPIPE to the calling process
*/
pfd = open(IPIPE, O_RDWR);
if (pfd < 0)
return;
prec.pd_pid = pid;
prec.pd_type = cmd;
(void) write(pfd, &prec, sizeof (pidrec_t));
(void) close(pfd);
}
/*
* makeutx - create a utmpx entry, recycling an id if a wild card is
* specified. Also notify init about the new pid
*
* args: utmpx - point to utmpx structure to be created
*/
struct utmpx *
makeutx(const struct utmpx *utmp)
{
struct utmpx *utp;
struct futmpx *ut; /* "current" utmpx being examined */
unsigned char saveid[IDLEN]; /* the last id we matched that was */
/* NOT a dead proc */
int falphanum = 0x30; /* first alpha num char */
off_t offset;
/*
* Are any wild card char's present in the idlen string?
*/
if (memchr(utmp->ut_id, SC_WILDC, IDLEN) != NULL) {
/*
* try to lock the utmpx file, only needed if
* we're doing wildcard matching
*/
if (lockutx())
return (NULL);
/*
* used in allocid
*/
(void) memset(saveid, falphanum, IDLEN);
while (ut = getoneutx(&offset))
if (idcmp(utmp->ut_id, ut->ut_id)) {
continue;
} else {
/*
* Found a match. We are done if this is
* a free slot. Else record this id. We
* will need it to generate the next new id.
*/
if (ut->ut_type == DEAD_PROCESS)
break;
else
(void) memcpy(saveid, ut->ut_id,
IDLEN);
}
if (ut) {
/*
* Unused entry, reuse it. We know the offset. So
* just go to that offset utmpx and write it out.
*/
(void) memcpy((caddr_t)utmp->ut_id, ut->ut_id, IDLEN);
putoneutx(utmp, offset);
updwtmpx(WTMPX_FILE, (struct utmpx *)utmp);
unlockutx();
sendpid(ADDPID, (pid_t)utmp->ut_pid);
return ((struct utmpx *)utmp);
} else {
/*
* nothing available, allocate an id and
* write it out at the end.
*/
if (allocid((char *)utmp->ut_id, saveid)) {
unlockutx();
return (NULL);
} else {
/*
* Seek to end and write out the entry
* and also update the utmpx file.
*/
(void) lseek(fd, 0L, SEEK_END);
offset = lseek(fd, 0L, SEEK_CUR);
putoneutx(utmp, offset);
updwtmpx(WTMPX_FILE, (struct utmpx *)utmp);
unlockutx();
sendpid(ADDPID, (pid_t)utmp->ut_pid);
return ((struct utmpx *)utmp);
}
}
} else {
utp = pututxline(utmp);
if (utp)
updwtmpx(WTMPX_FILE, utp);
endutxent();
sendpid(ADDPID, (pid_t)utmp->ut_pid);
return (utp);
}
}
/*
* makeut - create a utmp entry, recycling an id if a wild card is
* specified. Also notify init about the new pid
*
* args: utmp - point to utmp structure to be created
*/
struct utmp *
makeut(struct utmp *utmp)
{
struct utmpx utmpx;
struct utmpx *utmpx2;
if (compat_utmpflag)
return (_compat_makeut(utmp));
getutmpx(utmp, &utmpx);
if ((utmpx2 = makeutx(&utmpx)) == NULL)
return (NULL);
getutmp(utmpx2, utmp);
return (utmp);
}
#define UTMPNBUF 200 /* Approx 8k (FS Block) size */
static struct futmpx *utmpbuf = NULL;
/*
* Buffered read routine to get one entry from utmpx file
*/
static struct futmpx *
getoneutx(off_t *off)
{
static size_t idx = 0; /* Current index in the utmpbuf */
static size_t nidx = 0; /* Max entries in this utmpbuf */
static int nbuf = 0; /* number of utmpbufs read from disk */
ssize_t nbytes, bufsz = sizeof (struct futmpx) * UTMPNBUF;
if (utmpbuf == NULL)
if ((utmpbuf = malloc(bufsz)) == NULL) {
perror("malloc");
return (NULL);
}
if (idx == nidx) {
/*
* We have read all entries in the utmpbuf. Read
* the buffer from the disk.
*/
if ((nbytes = read(fd, utmpbuf, bufsz)) < bufsz) {
/*
* Partial read only. keep count of the
* number of valid entries in the buffer
*/
nidx = nbytes / sizeof (struct futmpx);
} else {
/*
* We read in the full UTMPNBUF entries
* Great !
*/
nidx = UTMPNBUF;
}
nbuf++; /* Number of buf we have read in. */
idx = 0; /* reset index within utmpbuf */
}
/*
* Current offset of this buffer in the file
*/
*off = (((nbuf - 1) * UTMPNBUF) + idx) * sizeof (struct futmpx);
if (idx < nidx) {
/*
* We still have at least one valid buffer in
* utmpbuf to be passed to the caller.
*/
return (&utmpbuf[idx++]);
}
/*
* Reached EOF. Return NULL. Offset is set correctly
* to append at the end of the file
*/
return (NULL);
}
static void
putoneutx(const struct utmpx *utpx, off_t off)
{
struct futmpx futx;
utmpx_api2frec(utpx, &futx);
(void) lseek(fd, off, SEEK_SET); /* seek in the utmpx file */
(void) write(fd, &futx, sizeof (futx));
}
/*
* sendupid - send message to utmpd to add or remove a pid from the
* list of procs to watch
*
* args: cmd - ADDPID or REMPID
* pid - process ID of process to watch
*/
static void
sendupid(int cmd, pid_t pid)
{
int pfd; /* file desc. for utmp pipe */
pidrec_t prec; /* place for message to be built */
/*
* if for some reason utmp didn't open utmppipe, open it read/write
* here to avoid sending SIGPIPE to the calling process
*/
pfd = open(UPIPE, O_RDWR | O_NONBLOCK | O_NDELAY);
if (pfd < 0)
return;
prec.pd_pid = pid;
prec.pd_type = cmd;
(void) write(pfd, &prec, sizeof (pidrec_t));
(void) close(pfd);
}
/*
* getutmpx - convert a utmp record into a utmpx record
*/
void
getutmpx(const struct utmp *ut, struct utmpx *utx)
{
(void) memcpy(utx->ut_user, ut->ut_user, sizeof (ut->ut_user));
(void) bzero(&utx->ut_user[sizeof (ut->ut_user)],
sizeof (utx->ut_user) - sizeof (ut->ut_user));
(void) memcpy(utx->ut_line, ut->ut_line, sizeof (ut->ut_line));
(void) bzero(&utx->ut_line[sizeof (ut->ut_line)],
sizeof (utx->ut_line) - sizeof (ut->ut_line));
(void) memcpy(utx->ut_id, ut->ut_id, sizeof (ut->ut_id));
utx->ut_pid = ut->ut_pid;
utx->ut_type = ut->ut_type;
utx->ut_exit = ut->ut_exit;
utx->ut_tv.tv_sec = ut->ut_time;
utx->ut_tv.tv_usec = 0;
utx->ut_session = 0;
bzero(utx->pad, sizeof (utx->pad));
bzero(utx->ut_host, sizeof (utx->ut_host));
utx->ut_syslen = 0;
}
/*
* getutmp - convert a utmpx record into a utmp record
*/
void
getutmp(const struct utmpx *utx, struct utmp *ut)
{
(void) memcpy(ut->ut_user, utx->ut_user, sizeof (ut->ut_user));
(void) memcpy(ut->ut_line, utx->ut_line, sizeof (ut->ut_line));
(void) memcpy(ut->ut_id, utx->ut_id, sizeof (utx->ut_id));
ut->ut_pid = utx->ut_pid;
ut->ut_type = utx->ut_type;
ut->ut_exit = utx->ut_exit;
ut->ut_time = utx->ut_tv.tv_sec;
}