Add utmp stuff

From: Roberto E. Vargas Caballero <k0ga_AT_shike2.com>
Date: Tue, 9 Oct 2012 18:29:27 +0200

When it is initiated a terminal emulator it is also created a new tty
(pseudo) and a new login is done. This imply that the usual job done by
login program must be done in the emulator, basically updating utmpx
database. With this patch sessions created by the emulator will be shown in
w or who.

Writing in utmpx means having enough privileges for writing in it, so the
binary needs have setgid. In this case we need this special privileges only
for two functions call, so it is not a security risk. Also, st can continue
working without setgid bit, but in this case utmpx stuff will not work.

utmpx stuff was standardised by POSIX in 2001, but some BSD distribution
lack good support for this. FreeBSD added this functions in January of 2012
and OpenBSD doesn't support them.
---
 Makefile |    3 ++
 st.c     |  100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 99 insertions(+), 4 deletions(-)
diff --git a/Makefile b/Makefile
index 8fc9674..8680e04 100644
--- a/Makefile
+++ b/Makefile
_AT_@ -5,6 +5,7 @@ include config.mk
 
 SRC = st.c
 OBJ = ${SRC:.c=.o}
+GROUP = utmp
 
 all: options st
 
_AT_@ -44,6 +45,8 @@ install: all
 	_AT_mkdir -p ${DESTDIR}${PREFIX}/bin
 	_AT_cp -f st ${DESTDIR}${PREFIX}/bin
 	_AT_chmod 755 ${DESTDIR}${PREFIX}/bin/st
+	_AT_chgrp $(GROUP) ${DESTDIR}${PREFIX}/bin/st
+	_AT_chmod g+s ${DESTDIR}${PREFIX}/bin/st
 	_AT_echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
 	_AT_mkdir -p ${DESTDIR}${MANPREFIX}/man1
 	_AT_sed "s/VERSION/${VERSION}/g" < st.1 > ${DESTDIR}${MANPREFIX}/man1/st.1
diff --git a/st.c b/st.c
index 46db942..8641a7a 100644
--- a/st.c
+++ b/st.c
_AT_@ -1,8 +1,10 @@
 /* See LICENSE for licence details. */
 #define _XOPEN_SOURCE 600
+#define _POSIX_SAVED_IDS
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <grp.h>
 #include <limits.h>
 #include <locale.h>
 #include <pwd.h>
_AT_@ -19,6 +21,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <time.h>
+#include <utmpx.h>
 #include <unistd.h>
 #include <X11/Xatom.h>
 #include <X11/Xlib.h>
_AT_@ -266,6 +269,9 @@ static void initenv(void);
 static void execsh(void);
 static void sigchld(int);
 static void run(void);
+static void delutmp(void);
+static void addutmp(int);
+static struct utmpx *findutmp(int);
 
 static void csidump(void);
 static void csihandle(void);
_AT_@ -374,6 +380,9 @@ static STREscape strescseq;
 static int cmdfd;
 static pid_t pid;
 static Selection sel;
+static gid_t egid, gid;
+struct utmpx utmp;
+struct passwd *pass;
 static int iofd = -1;
 static char **opt_cmd = NULL;
 static char *opt_io = NULL;
_AT_@ -900,6 +909,7 @@ void
 sigchld(int a) {
 	int stat = 0;
 
+	delutmp();
 	if(waitpid(pid, &stat, 0) < 0)
 		die("Waiting for pid %hd failed: %s\n",	pid, SERRNO);
 
_AT_@ -910,14 +920,86 @@ sigchld(int a) {
 	}
 }
 
+
+/*
+ * From utmp(5)
+ * xterm and other terminal emulators directly create a USER_PROCESS
+ * record and generate the ut_id  by using the string that suffix part of
+ * the terminal name (the characters  following  /dev/[pt]ty). If they find
+ * a DEAD_PROCESS for this ID, they recycle it, otherwise they create a new
+ * entry.  If they can, they will mark it as DEAD_PROCESS on exiting and it
+ * is advised that they null ut_line, ut_time, ut_user, and ut_host as well.
+*/
+
+struct utmpx *
+findutmp(int type) {
+	struct utmpx *r;
+
+	utmp.ut_type = type;
+	setutxent();
+	for(;;) {
+		/*
+		 * we can not use getutxline because we can search in
+		 * DEAD_PROCESS to
+		 */
+		if(!(r = getutxid(&utmp)))
+			break;
+		if(!strcmp(r->ut_line, utmp.ut_line))
+			break;
+		memset(r, 0, sizeof(*r)); /* for Solaris, IRIX64 and HPUX */
+	}
+	return r;
+}
+
+void
+addutmp(int fd) {
+	uint id;
+	char *pts = ptsname(fd), *cp, buf[5] = {'x'};
+
+	for(cp = pts + strlen(pts) - 1; isdigit(*cp); --cp)
+		/* nothing */;
+	if((id = atoi(++cp)) > 999 || strlen(pts + 5) > sizeof(utmp.ut_line))
+		die("Incorrect pts name %s\n", pts);
+	sprintf(buf + 1, "%03d", id);
+	strncpy(utmp.ut_id, buf, 4);
+	strcpy(utmp.ut_line, pts + 5);   /* remove /dev/ part of the string */
+
+	if(!findutmp(DEAD_PROCESS))
+		findutmp(USER_PROCESS);
+
+	utmp.ut_type = USER_PROCESS;
+	strcpy(utmp.ut_user, pass->pw_name);
+	utmp.ut_pid = pid;
+	utmp.ut_tv.tv_sec = time(NULL);
+	utmp.ut_tv.tv_usec = 0;
+	 /* don't use no standard fields host and session */
+
+	setgid(egid);
+	if(!pututxline(&utmp))
+		perror("add utmp entry");
+	setgid(gid);
+	endutxent();
+}
+
+void
+delutmp(void) {
+	struct utmpx *r;
+
+	setutxent();
+	if((r = getutxline(&utmp)) != NULL) {
+		r->ut_type = DEAD_PROCESS;
+		r->ut_tv.tv_usec = r->ut_tv.tv_sec = 0;
+		setgid(egid);
+		pututxline(r);
+		setgid(gid);
+	}
+	endutxent();
+}
+
 void
 initenv(void) {
-	const struct passwd *pass = getpwuid(getuid());
 	char buff[sizeof(long) * 8];
 
-	if(!pass)
-		die("Process is running with an incorrect uid\n");
-
 	unsetenv("COLUMNS");
 	unsetenv("LINES");
 	unsetenv("TERMCAP");
_AT_@ -943,7 +1025,13 @@ void
 ttynew(void) {
 	int m, s;
 	struct winsize w = {term.row, term.col, 0, 0};
+	uid_t id = getuid();
 
+	pass = getpwuid(id);
+	if(!pass || !pass->pw_name ||
+	   strlen(pass->pw_name) + 1 > sizeof(utmp.ut_user)) {
+		die("Process is running with an incorrect uid %d\n", id);
+	}
 	/* seems to work fine on linux, openbsd and freebsd */
 	if(openpty(&m, &s, NULL, NULL, &w) < 0)
 		die("openpty failed: %s\n", SERRNO);
_AT_@ -967,6 +1055,7 @@ ttynew(void) {
 	default:
 		close(s);
 		cmdfd = m;
+		addutmp(cmdfd);
 		signal(SIGCHLD, sigchld);
 		if(opt_io) {
 			iofd = (!strcmp(opt_io, "-")) ?
_AT_@ -2761,6 +2850,9 @@ main(int argc, char *argv[]) {
 	int i, bitm, xr, yr;
 	uint wr, hr;
 
+	egid = getegid();
+	gid = getgid();
+	setgid(gid);
 	xw.fw = xw.fh = xw.fx = xw.fy = 0;
 	xw.isfixed = False;
 
-- 
1.7.10.4
--Fba/0zbH8Xs+Fj9o--
Received on Mon Sep 17 2001 - 00:00:00 CEST

This archive was generated by hypermail 2.3.0 : Tue Oct 09 2012 - 19:12:02 CEST