/* * 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 2008 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. */ /* * 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. */ #include "lint.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; int cancel_state; 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", (int)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; } (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); do { w = waitpid(child, &status, 0); } while (w == -1 && errno == EINTR); (void) pthread_setcancelstate(cancel_state, NULL); /* * 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) < 0) 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; }