Util-Linux package development
 help / color / mirror / Atom feed
* [PATCH] Secure login and sulogin on S390x
@ 2026-04-29  9:28 Werner Fink
  2026-05-04 11:31 ` Dr. Werner Fink
  2026-05-04 18:53 ` Chris Hofstaedtler
  0 siblings, 2 replies; 6+ messages in thread
From: Werner Fink @ 2026-04-29  9:28 UTC (permalink / raw)
  To: util-linux; +Cc: Werner Fink

Some remarks: on S390x architecture of modern zSeries the hypervisor
does log the console I/O.  For both the 3215 half duplex line mode as
well as the 3270 full-screen/block mode console type the I/O is logged
on the hypervisor's side.  To control this there are command send via
/dev/vmcp to tell the  z/VM control program of the hypervisor not to
log during entering the password.  For the 3215 console also automatic
scroll is enabled which avoid to press CLEAR to get the password prompt
if on the next block.

Beside this for the ttysclp0, the serial ASCII terminal accessed via
a websocket has since ncurses 6.5-20250405 a new terminfo entry called
"sclp" to support not only the vt220 like behaviour but also the
colouring feature of ttysclp0.

Signed-off-by: Werner Fink <werner@suse.de>
---
 lib/ttyutils.c                 |   5 +-
 login-utils/Makemodule.am      |   6 +-
 login-utils/login.c            |  85 +++++++++++-
 login-utils/meson.build        |   5 +
 login-utils/sulogin-consoles.c |  14 ++
 login-utils/sulogin-consoles.h |   4 +
 login-utils/sulogin.c          |  64 +++++++--
 login-utils/vmcp.c             | 236 +++++++++++++++++++++++++++++++++
 login-utils/vmcp.h             |  29 ++++
 9 files changed, 436 insertions(+), 12 deletions(-)
 create mode 100644 login-utils/vmcp.c
 create mode 100644 login-utils/vmcp.h

diff --git a/lib/ttyutils.c b/lib/ttyutils.c
index bacd73077..3085419bd 100644
--- a/lib/ttyutils.c
+++ b/lib/ttyutils.c
@@ -21,7 +21,7 @@
 #  if defined (__s390__) || defined (__s390x__)
 #    define DEFAULT_TTYS0  "dumb"
 #    define DEFAULT_TTY32  "ibm327x"
-#    define DEFAULT_TTYS1  "vt220"
+#    define DEFAULT_TTYS1  "sclp"
 #  endif
 #  ifndef DEFAULT_STERM
 #    define DEFAULT_STERM  "vt102"
@@ -176,7 +176,8 @@ char *get_terminal_default_type(const char *ttyname, int is_serial)
 		 * Special terminal on first serial line on a S/390(x) which
 		 * is due legacy reasons a block terminal of type 3270 or
 		 * higher.  Whereas the second serial line on a S/390(x) is
-		 * a real character terminal which is compatible with VT220.
+		 * a real character terminal which is compatible with "sclp"
+		 * since ncurses 6.5-20250405 for e.g. color support.
 		 */
 		if (strcmp(ttyname, "ttyS0") == 0)		/* linux/drivers/s390/char/con3215.c */
 			return strdup(DEFAULT_TTYS0);
diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am
index d288a06c3..599547f7b 100644
--- a/login-utils/Makemodule.am
+++ b/login-utils/Makemodule.am
@@ -33,7 +33,9 @@ dist_noinst_DATA += login-utils/sulogin.8.adoc
 sulogin_SOURCES = \
 	login-utils/sulogin.c \
 	login-utils/sulogin-consoles.c \
-	login-utils/sulogin-consoles.h
+	login-utils/sulogin-consoles.h \
+	login-utils/vmcp.c \
+	login-utils/vmcp.h
 if USE_PLYMOUTH_SUPPORT
 sulogin_SOURCES += lib/plymouth-ctrl.c
 endif
@@ -59,6 +61,8 @@ MANPAGES += login-utils/login.1
 dist_noinst_DATA += login-utils/login.1.adoc
 login_SOURCES = \
 	login-utils/login.c \
+	login-utils/vmcp.c \
+	login-utils/vmcp.h \
 	lib/logindefs.c
 login_LDADD = $(LDADD) libcommon.la -lpam
 if HAVE_LINUXPAM
diff --git a/login-utils/login.c b/login-utils/login.c
index d9e93d0dd..5b10cb83d 100644
--- a/login-utils/login.c
+++ b/login-utils/login.c
@@ -85,6 +85,7 @@
 #include "pwdutils.h"
 
 #include "logindefs.h"
+#include "vmcp.h"
 
 #define LOGIN_MAX_TRIES        3
 #define LOGIN_EXIT_TIMEOUT     5
@@ -161,6 +162,85 @@ static int is_consoletty(int fd)
 }
 #endif
 
+#if defined(__s390__) || defined(__s390x__)
+/* Avoid that passwords are logged on the hypervisors console log */
+
+#define CON_3215	0x0010	/* s390x 3215 halfduplex console */
+#define CON_3270	0x0020	/* s390x 3270 console */
+#define CON_SCLP	0x0040	/* s390x sclp terminals */
+
+struct s390con {
+	int flags;
+	int vmcpfd;
+};
+
+static struct s390con s390_console_spool_stop(int fd)
+{
+	struct stat stb;
+	struct s390con con = { .flags = 0, .vmcpfd = -1 };
+
+	if ((fstat(fd, &stb) >= 0)) {
+		if (major(stb.st_rdev) == 4 && minor(stb.st_rdev) == 64)
+			con.flags |= CON_3215;
+		if (major(stb.st_rdev) == 4 && minor(stb.st_rdev) >= 65)
+			con.flags |= CON_SCLP;
+		else if (major(stb.st_rdev) == 227 && minor(stb.st_rdev) >= 1)
+			con.flags |= CON_3270;
+	}
+	if (con.flags & (CON_3215|CON_3270)) {
+		con.vmcpfd = openvmcp();
+		if (con.vmcpfd >= 0) {
+			char *msg = queryspool(con.vmcpfd);
+			if (msg) {
+				parsespool(msg);
+				free(msg);
+			}
+			/*
+			 * Avoid that console log during
+			 * password input
+			 */
+			stopspool(con.vmcpfd);
+		}
+	}
+	if (con.flags & CON_3215) {
+		if (con.vmcpfd >= 0) {
+			char *msg = queryterm(con.vmcpfd);
+			if (msg) {
+				parseterm(msg);
+				free(msg);
+			}
+			/*
+			 * Avoid that CLEAR has to be pressed for
+			 * password input
+			 */
+			setterm(con.vmcpfd, "0");
+# ifdef WARN_WITH_VMCP
+			warning3215(con.vmcpfd);
+# endif
+		}
+	}
+	return con;
+}
+
+static void s390_console_spool_restore(struct s390con *con)
+{
+	if (con->vmcpfd >= 0) {
+		if (con->flags & CON_3215)
+			restoreterm(con->vmcpfd);
+		if (con->flags & (CON_3215|CON_3270))
+			restorespool(con->vmcpfd);
+		clearvmcp();
+		close(con->vmcpfd);
+		con->vmcpfd = -1;
+	}
+}
+# define PREPARE_CONSOLE(fd)	struct s390con con = s390_console_spool_stop(fd)
+# define RESTORE_CONSOLE()	s390_console_spool_restore(&con)
+#else
+# define PREPARE_CONSOLE(fd)	/* */
+# define RESTORE_CONSOLE()	/* */
+#endif
+
 /*
  * Robert Ambrose writes:
  * A couple of my users have a problem with login processes hanging around
@@ -1580,8 +1660,11 @@ int main(int argc, char **argv)
 	/* login -f, then the user has already been authenticated */
 	cxt.noauth = cxt.noauth && getuid() == 0 ? 1 : 0;
 
-	if (!cxt.noauth)
+	if (!cxt.noauth) {
+		PREPARE_CONSOLE(0);
 		loginpam_auth(&cxt);
+		RESTORE_CONSOLE();
+        }
 
 	/*
 	 * Authentication may be skipped (for example, during krlogin, rlogin,
diff --git a/login-utils/meson.build b/login-utils/meson.build
index 251d8de46..5f6310b01 100644
--- a/login-utils/meson.build
+++ b/login-utils/meson.build
@@ -49,6 +49,7 @@ test_islocal_sources = files(
 
 test_consoles_sources = files(
   'sulogin-consoles.c',
+  'vmcp.c',
 )
 
 last_sources = files(
@@ -59,6 +60,8 @@ last_manadocs = files('last.1.adoc')
 
 login_sources = files(
   'login.c',
+  'vmcp.c',
+  'vmcp.h',
 )
 login_manadocs = files('login.1.adoc')
 
@@ -66,6 +69,8 @@ sulogin_sources = files(
   'sulogin.c',
   'sulogin-consoles.c',
   'sulogin-consoles.h',
+  'vmcp.c',
+  'vmcp.h',
 )
 sulogin_manadocs = files('sulogin.8.adoc')
 
diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c
index c35922ff1..f09d90ece 100644
--- a/login-utils/sulogin-consoles.c
+++ b/login-utils/sulogin-consoles.c
@@ -389,6 +389,20 @@ static int detect_consoles_from_proc(struct list_head *consoles)
 		free(name);
 		if (rc < 0)
 			goto done;
+		if (!list_empty(consoles)) {
+			struct console *last;
+			last = list_last_entry(consoles, struct console, entry);
+			if (!strchr(fbuf, 'C'))
+				last->flags |= CON_CONSDEV;
+#if defined(__s390__) || defined(__s390x__)
+			if (maj == 4 && min == 64)
+				last->flags |= CON_3215;
+			if (maj == 4 && min >= 65)
+				last->flags |= CON_SCLP;
+			else if (maj == 227 && min >= 1)
+				last->flags |= CON_3270;
+#endif
+		}
 	}
 
 	rc = list_empty(consoles) ? 1 : 0;
diff --git a/login-utils/sulogin-consoles.h b/login-utils/sulogin-consoles.h
index d3b4298e4..10fdf612c 100644
--- a/login-utils/sulogin-consoles.h
+++ b/login-utils/sulogin-consoles.h
@@ -39,6 +39,10 @@ struct console {
 #define	CON_SERIAL	0x0001
 #define	CON_NOTTY	0x0002
 #define	CON_EIO		0x0004
+#define	CON_CONSDEV	0x0008	/* Last on the kernel command line */
+#define	CON_3215	0x0010	/* s390x 3215 halfduplex console */
+#define	CON_3270	0x0020  /* s390x 3270 console */
+#define	CON_SCLP	0x0040	/* s390x sclp terminals */
 	pid_t pid;
 	struct chardata cp;
 	struct termios tio;
diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c
index 23f5164e6..1984e6912 100644
--- a/login-utils/sulogin.c
+++ b/login-utils/sulogin.c
@@ -66,6 +66,7 @@
 #include "strutils.h"
 #include "ttyutils.h"
 #include "sulogin-consoles.h"
+#include "vmcp.h"
 #define CONMAX		16
 
 static unsigned int timeout;
@@ -218,7 +219,7 @@ static void tcinit(struct console *con)
 #endif
 
 #ifdef KDGKBMODE
-	if (!(con->flags & CON_SERIAL)
+	if (!(con->flags & (CON_SERIAL|CON_3215|CON_3270|CON_SCLP))
 	    && ioctl(fd, KDGKBMODE, &mode) < 0)
 		con->flags |= CON_SERIAL;
 	errno = 0;
@@ -265,6 +266,13 @@ static void tcinit(struct console *con)
 		}
 	}
 
+#if defined(__s390__) || defined(__s390x__)
+	if (con->flags & (CON_3215|CON_3270)) {
+		setlocale(LC_CTYPE, "POSIX");
+		setlocale(LC_MESSAGES, "POSIX");
+		goto setattr;
+	}
+#endif
 	/* Handle lines other than virtual consoles here */
 #if defined(KDGKBMODE) || defined(TIOCGSERIAL)
 	if (con->flags & CON_SERIAL)
@@ -292,7 +300,7 @@ static void tcinit(struct console *con)
 		cfsetospeed(tio, ospeed);
 
 #ifdef HAVE_STRUCT_TERMIOS_C_LINE
-		tio->c_line         = 0;
+		tio->c_line	 = 0;
 #endif
 		tio->c_cc[VTIME]    = 0;
 		tio->c_cc[VMIN]     = 1;
@@ -364,6 +372,10 @@ static void tcfinal(struct console *con)
 		free(term);
 	}
 
+#if defined(__s390__) || defined(__s390x__)
+	if (con->flags & (CON_3215|CON_3270))
+		return;
+#endif
 	if (!(con->flags & CON_SERIAL) || (con->flags & CON_NOTTY))
 		return;
 
@@ -983,9 +995,9 @@ static void usage(void)
 	fputs(_("Single-user login.\n"), out);
 
 	fputs(USAGE_OPTIONS, out);
-	fputs(_(" -p, --login-shell        start a login shell\n"
+	fputs(_(" -p, --login-shell	start a login shell\n"
 		" -t, --timeout <seconds>  max time to wait for a password (default: no limit)\n"
-		" -e, --force              examine password files directly if getpwnam(3) fails\n"),
+		" -e, --force	      examine password files directly if getpwnam(3) fails\n"),
 		out);
 
 	fputs(USAGE_SEPARATOR, out);
@@ -1012,8 +1024,8 @@ int main(int argc, char **argv)
 	static const struct option longopts[] = {
 		{ "login-shell",  no_argument,       NULL, 'p' },
 		{ "timeout",      required_argument, NULL, 't' },
-		{ "force",        no_argument,       NULL, 'e' },
-		{ "help",         no_argument,       NULL, 'h' },
+		{ "force",	no_argument,       NULL, 'e' },
+		{ "help",	 no_argument,       NULL, 'h' },
 		{ "version",      no_argument,       NULL, 'V' },
 		{ NULL, 0, NULL, 0 }
 	};
@@ -1193,10 +1205,46 @@ int main(int argc, char **argv)
 				char *answer;
 				int doshell = 0;
 				int deny = !opt_e && locked_account_password(pwd->pw_passwd);
-
+#if defined(__s390__) || defined(__s390x__)
+				int vmcpfd = -1;
+				if (con->flags & (CON_3215|CON_3270)) {
+					vmcpfd = openvmcp();
+					if (vmcpfd >= 0) {
+						char *msg = queryspool(vmcpfd);
+						if (msg) {
+							parsespool(msg);
+							free(msg);
+						}
+						stopspool(vmcpfd);
+					}
+				}
+				if (con->flags & CON_3215) {
+					if (vmcpfd >= 0) {
+						char *msg = queryterm(vmcpfd);
+						if (msg) {
+							parseterm(msg);
+							free(msg);
+						}
+						setterm(vmcpfd, "0");
+						warning3215(vmcpfd);
+					}
+				}
+#endif
 				doprompt(passwd, con, deny);
 
-				if ((answer = getpasswd(con)) == NULL)
+				answer = getpasswd(con);
+#if defined(__s390__) || defined(__s390x__)
+				if (vmcpfd >= 0) {
+					if (con->flags & CON_3215)
+						restoreterm(vmcpfd);
+					if (con->flags & (CON_3215|CON_3270))
+						restorespool(vmcpfd);
+					clearvmcp();
+					close(vmcpfd);
+					vmcpfd = -1;
+				}
+#endif
+				if (answer == NULL)
 					break;
 				if (deny) {
 #ifdef HAVE_EXPLICIT_BZERO
diff --git a/login-utils/vmcp.c b/login-utils/vmcp.c
new file mode 100644
index 000000000..52c2d4a6b
--- /dev/null
+++ b/login-utils/vmcp.c
@@ -0,0 +1,236 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * vmcp.c - s390x 3215 console spool and terminal management
+ *
+ * Copyright 2026 Werner Fink, SUSE Software Solutions Germany GmbH
+ *
+ * Based on:
+ *
+ * Copyright IBM Corp. 2018
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+int isinteger(const char *str)
+{
+    int errs = errno, ret = 1;
+    long __attribute__((__unused__)) val;
+    char *endptr;
+
+    errno = 0;
+    val = strtol(str, &endptr, 10);
+
+    /* Check if no digits were found at all */
+    if (str == endptr)
+	ret = 0;
+
+    /* Check for overflow or underflow */
+    if (errno == ERANGE)
+	ret = 0;
+
+    /* Check for trailing non-numeric characters (e.g., "123abc") */
+    if (*endptr != '\0')
+	ret = 0;
+
+    errno = errs;
+    return ret;
+}
+
+#if defined(__s390__) || defined(__s390x__)
+
+#define	VMCP_DEVICE_NODE	"/dev/vmcp"
+#define	VMCP_GETSIZE		_IOR(0x10, 3, int)
+#define	VMCP_SETBUF		_IOW(0x10, 2, int)
+#define	VMCP_GETCODE		_IOR(0x10, 1, int)
+
+static char *more, *hold;
+static int spooling;
+
+int openvmcp(void)
+{
+    return open(VMCP_DEVICE_NODE, O_RDWR|O_NOCTTY);
+}
+
+void clearvmcp(void)
+{
+    if (more) {
+	free(more);
+	more = NULL;
+    }
+    if (hold) {
+	free(hold);
+	hold = NULL;
+    }
+}
+
+static char* askvmcp(int fd, const char *question)
+{
+    long pagesize = sysconf(_SC_PAGESIZE);
+    int rc = 0, num, buffersize;
+    char* ret = NULL;
+
+    num = (strlen(question) + pagesize - 1)/pagesize;
+    buffersize = num * pagesize;
+
+    if (ioctl(fd, VMCP_SETBUF, &buffersize) == -1)
+	goto out;
+    do {
+	rc = write(fd, question, strlen(question));
+	if (rc < 0) {
+	    if (errno != EINTR)
+		goto out;
+	}
+    } while (rc < 0);
+    if (ioctl(fd, VMCP_GETCODE, &rc) == -1)
+	goto out;
+    if (ioctl(fd, VMCP_GETSIZE, &buffersize) == -1)
+	goto out;
+    ret = (char*)malloc(buffersize);
+    if (!ret)
+	goto out;
+    do {
+	rc = read(fd, ret, buffersize);
+	if (rc < 0) {
+	    if (errno != EINTR)
+		goto out;
+	}
+    } while (rc < 0);
+out:
+    return ret;
+}
+
+char* queryterm(int fd)
+{
+    const char* question = "QUERY TERMINAL";
+    return askvmcp(fd, question);
+}
+
+char* queryspool(int fd)
+{
+    const char* question = "QUERY VIRTUAL CONSOLE";
+    return askvmcp(fd, question);
+}
+
+static int writevmcp(int fd, char *instruction)
+{
+    long pagesize = sysconf(_SC_PAGESIZE);
+    int rc = 0, num, buffersize;
+    int ret = -1;
+
+    num = (strlen(instruction) + pagesize - 1)/pagesize;
+    buffersize = num * pagesize;
+
+    if (ioctl(fd, VMCP_SETBUF, &buffersize) == -1)
+	goto out;
+    do {
+	rc = write(fd, instruction, strlen(instruction));
+	if (rc < 0) {
+	    if (errno != EINTR)
+		goto out;
+	}
+    } while (rc < 0);
+    if (ioctl(fd, VMCP_GETCODE, &rc) == -1)
+	goto out;
+    if (ioctl(fd, VMCP_GETSIZE, &buffersize) == -1)
+	goto out;
+    if (rc == 0 && buffersize == 0)
+	ret = 0;
+out:
+    return ret;
+}
+
+int setterm(int fd, char *tout)
+{
+    char *instruction;
+    int ret = -1;
+
+    if (asprintf(&instruction, "TERMINAL MORE %s 0 HOLD OFF", tout) == -1)
+	goto out;
+
+    ret = writevmcp(fd, instruction);
+    free(instruction);
+out:
+    return ret;
+}
+
+int restoreterm(int fd)
+{
+    char* instruction;
+    int ret = -1;
+
+    if (!more || !hold)
+	goto out;
+    if (asprintf(&instruction, "TERMINAL %s %s", more, hold) == -1)
+	goto out;
+
+    ret = writevmcp(fd, instruction);
+    free(instruction);
+out:
+    return ret;
+}
+
+int stopspool(int fd)
+{
+    char* instruction = "SPOOL CONSOLE STOP";
+    if (!spooling)
+	return 1;
+    return writevmcp(fd, instruction);
+}
+
+int restorespool(int fd)
+{
+    char* instruction = "SPOOL CONSOLE START";
+    if (!spooling)
+	return 1;
+    return writevmcp(fd, instruction);
+}
+
+void parseterm(char *msg)
+{
+    int n;
+    char *token, *ptr;
+    for (n = 1, ptr = msg; ; n++, ptr = NULL) {
+	token = strtok(ptr, ",\n");
+	if (!token)
+	    break;
+	if (hold && more)
+	    break;
+	while (*token == ' ')
+	    token++;
+	if (strncmp("MORE ", token, 5) == 0)
+	    more = strdup(token);
+	if (strncmp("HOLD ", token, 5) == 0)
+	    hold = strdup(token);
+    }
+}
+
+void parsespool(char *msg)
+{
+    spooling = 0;
+    if (strstr(msg, " TERM START ") != NULL)
+	spooling = 1;
+}
+
+void warning3215(int fd)
+{
+    /*
+     * Warning: Do not translate this test as it might inlude then so called
+     * umlauts which in fact can not be encoded for the 3215 console interface.
+     * The 3215 console driver work in fact with EBCDIC codepage and the
+     * kernel has to translate such umlauts (multi or single bytes) with the
+     * correct EBCDIC character table (for german e.g. IBM-1141 or IBM-273).
+     */
+    (void) writevmcp(fd, "MESSAGE * WARNING: 3215 mode. Password visible!");
+    (void) writevmcp(fd, "MESSAGE * Ensure nobody is watching the screen.");
+}
+#endif
diff --git a/login-utils/vmcp.h b/login-utils/vmcp.h
new file mode 100644
index 000000000..50ef1ba90
--- /dev/null
+++ b/login-utils/vmcp.h
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * vmcp.c - s390x 3215 console spool and terminal management
+ *
+ * Copyright 2026 Werner Fink, SUSE Software Solutions Germany GmbH
+ *
+ * Based on:
+ *
+ * Copyright IBM Corp. 2018
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ */
+
+extern int isinteger(const char *str);
+#if defined(__s390__) || defined(__s390x__)
+extern int openvmcp(void);
+extern void clearvmcp(void);
+extern char* queryterm(int fd);
+extern char* queryspool(int fd);
+extern int setterm(int fd, char *tout);
+extern int stopspool(int fd);
+extern int restoreterm(int fd);
+extern int restorespool(int fd);
+extern void parseterm(char *msg);
+extern void parsespool(char *msg);
+extern void warning3215(int fd);
+#endif
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH] Secure login and sulogin on S390x
  2026-04-29  9:28 [PATCH] Secure login and sulogin on S390x Werner Fink
@ 2026-05-04 11:31 ` Dr. Werner Fink
  2026-05-04 18:53 ` Chris Hofstaedtler
  1 sibling, 0 replies; 6+ messages in thread
From: Dr. Werner Fink @ 2026-05-04 11:31 UTC (permalink / raw)
  To: util-linux


[-- Attachment #1.1: Type: text/plain, Size: 234 bytes --]

Hi,

just to extend the last patch with a further one ro catch
all corner cases as well.

Werner

-- 
  "Having a smoking section in a restaurant is like having
          a peeing section in a swimming pool." -- Edward Burr

[-- Attachment #1.2: x.patch --]
[-- Type: text/x-patch, Size: 3949 bytes --]

From a6c23b4d8ad84fff4a58855b76b56a261ff8aa5e Mon Sep 17 00:00:00 2001
From: Werner Fink <werner@suse.de>
Date: Mon, 4 May 2026 13:20:13 +0200
Subject: [PATCH] Ensure to detect all special console case on S390x

Signed-off-by: Werner Fink <werner@suse.de>
---
 login-utils/sulogin-consoles.c | 46 ++++++++++++++++++++++------------
 1 file changed, 30 insertions(+), 16 deletions(-)

diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c
index f09d90ece..4cce4f078 100644
--- a/login-utils/sulogin-consoles.c
+++ b/login-utils/sulogin-consoles.c
@@ -312,7 +312,7 @@ static
 #ifdef __GNUC__
 __attribute__((__hot__))
 #endif
-int append_console(struct list_head *consoles, const char * const name)
+int append_console(struct list_head *consoles, const char * const name, dev_t dev)
 {
 	struct console *restrict tail;
 	const struct console *last = NULL;
@@ -343,7 +343,29 @@ int append_console(struct list_head *consoles, const char * const name)
 	tail->reset_tty_context = NULL;
 	tail->user_tty_context = NULL;
 #endif
-
+#if defined(__s390__) || defined(__s390x__)
+	/*
+	 * Stat the device path to determine its major/minor numbers. 
+	 * This ensures we detect s390x terminal types regardless of whether 
+	 * the console was found via /proc, /sys, cmdline, or a direct stdin 
+	 * fallback (e.g. sulogin < /dev/ttyS0).
+	 */
+	if (!dev) {
+		struct stat st;
+		if (stat(name, &st) == 0 && S_ISCHR(st.st_mode))
+			dev = st.st_rdev;
+	}
+	if (dev) {
+		unsigned int maj = major(dev);
+		unsigned int min = minor(dev);
+		if (maj == 4 && min == 64)
+			tail->flags |= CON_3215;
+		else if (maj == 4 && min >= 65)
+			tail->flags |= CON_SCLP;
+		else if (maj == 227 && min >= 1)
+			tail->flags |= CON_3270;
+	}
+#endif
 	return 0;
 }
 
@@ -385,7 +407,7 @@ static int detect_consoles_from_proc(struct list_head *consoles)
 		name = scandev(dir, comparedev);
 		if (!name)
 			continue;
-		rc = append_console(consoles, name);
+		rc = append_console(consoles, name, comparedev);
 		free(name);
 		if (rc < 0)
 			goto done;
@@ -394,14 +416,6 @@ static int detect_consoles_from_proc(struct list_head *consoles)
 			last = list_last_entry(consoles, struct console, entry);
 			if (!strchr(fbuf, 'C'))
 				last->flags |= CON_CONSDEV;
-#if defined(__s390__) || defined(__s390x__)
-			if (maj == 4 && min == 64)
-				last->flags |= CON_3215;
-			if (maj == 4 && min >= 65)
-				last->flags |= CON_SCLP;
-			else if (maj == 227 && min >= 1)
-				last->flags |= CON_3270;
-#endif
 		}
 	}
 
@@ -461,7 +475,7 @@ static int detect_consoles_from_sysfs(struct list_head *consoles)
 		name = scandev(dir, comparedev);
 		if (!name)
 			continue;
-		rc = append_console(consoles, name);
+		rc = append_console(consoles, name, comparedev);
 		free(name);
 		if (rc < 0)
 			goto done;
@@ -549,7 +563,7 @@ static int detect_consoles_from_cmdline(struct list_head *consoles)
 		name = scandev(dir, comparedev);
 		if (!name)
 			continue;
-		rc = append_console(consoles, name);
+		rc = append_console(consoles, name, comparedev);
 		free(name);
 		if (rc < 0)
 			goto done;
@@ -607,7 +621,7 @@ static int detect_consoles_from_tiocgdev(struct list_head *consoles,
 			goto done;
 		}
 	}
-	rc = append_console(consoles, name);
+	rc = append_console(consoles, name, comparedev);
 	free(name);
 	if (rc < 0)
 		goto done;
@@ -721,7 +735,7 @@ int detect_consoles(const char *device, const int fallback, struct list_head *co
 		closedir(dir);
 
 		if (name) {
-			rc = append_console(consoles, name);
+			rc = append_console(consoles, name, comparedev);
 			free(name);
 			if (rc < 0)
 				return rc;
@@ -798,7 +812,7 @@ fallback:
 		n = strdup(name);
 		if (!n)
 			return -ENOMEM;
-		rc = append_console(consoles, n);
+		rc = append_console(consoles, n, 0);
 		free(n);
 		if (rc < 0)
 			return rc;
-- 
2.51.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 894 bytes --]

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH] Secure login and sulogin on S390x
  2026-04-29  9:28 [PATCH] Secure login and sulogin on S390x Werner Fink
  2026-05-04 11:31 ` Dr. Werner Fink
@ 2026-05-04 18:53 ` Chris Hofstaedtler
  2026-05-05  9:39   ` Dr. Werner Fink
  1 sibling, 1 reply; 6+ messages in thread
From: Chris Hofstaedtler @ 2026-05-04 18:53 UTC (permalink / raw)
  To: Werner Fink; +Cc: util-linux

* Werner Fink <werner@suse.de> [260429 11:30]:
>Some remarks: on S390x architecture of modern zSeries the hypervisor
>does log the console I/O.  For both the 3215 half duplex line mode as
>well as the 3270 full-screen/block mode console type the I/O is logged
>on the hypervisor's side.  To control this there are command send via
>/dev/vmcp to tell the  z/VM control program of the hypervisor not to
>log during entering the password.  For the 3215 console also automatic
>scroll is enabled which avoid to press CLEAR to get the password prompt
>if on the next block.

[..]
> 9 files changed, 436 insertions(+), 12 deletions(-)

This seems to add a lot of code to util-linux. I was wondering if 
sulogin is the only place that needs this log filtering. What 
about su, sudo, doas and so on? Note that there are different su 
(and probably doas) versions in use across distributions.

Wouldn't it be better if the hypervisor would not log input if the 
terminal output was disabled? That seems like behaviour commonly 
found in other virtual terminal emulators.

Best,
Chris


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] Secure login and sulogin on S390x
  2026-05-04 18:53 ` Chris Hofstaedtler
@ 2026-05-05  9:39   ` Dr. Werner Fink
  2026-05-21  9:12     ` Repost: " Dr. Werner Fink
  0 siblings, 1 reply; 6+ messages in thread
From: Dr. Werner Fink @ 2026-05-05  9:39 UTC (permalink / raw)
  To: Chris Hofstaedtler; +Cc: util-linux

[-- Attachment #1: Type: text/plain, Size: 2049 bytes --]

On 2026/05/04 20:53:30 +0200, Chris Hofstaedtler wrote:
> * Werner Fink <werner@suse.de> [260429 11:30]:
> > Some remarks: on S390x architecture of modern zSeries the hypervisor
> > does log the console I/O.  For both the 3215 half duplex line mode as
> > well as the 3270 full-screen/block mode console type the I/O is logged
> > on the hypervisor's side.  To control this there are command send via
> > /dev/vmcp to tell the  z/VM control program of the hypervisor not to
> > log during entering the password.  For the 3215 console also automatic
> > scroll is enabled which avoid to press CLEAR to get the password prompt
> > if on the next block.
> 
> [..]
> > 9 files changed, 436 insertions(+), 12 deletions(-)
> 
> This seems to add a lot of code to util-linux. I was wondering if sulogin is
> the only place that needs this log filtering. What about su, sudo, doas and
> so on? Note that there are different su (and probably doas) versions in use
> across distributions.
> 
> Wouldn't it be better if the hypervisor would not log input if the terminal
> output was disabled? That seems like behaviour commonly found in other
> virtual terminal emulators.
> 
> Best,
> Chris

Maybe other hypervisors have the same behaviour.  Nevertheless the
3215 console is half duplex printer like a punchcard based teletypewriter
running within the hypervisor of an zSystem firmware.  Means every zOS and
Linux instances are VMs on such zSystem.  The 3270 console type is a bit
better as it understands escape sequences and is a block mode console type,
but still you have primitiv local prompt within the hypervisor and not on
the linux side its self.   You can compare this with qemu within virt-manager,
but AFAIK qemu does not log keystrokes.

The vmcp.c is a copy of the version I use for my blogd in the showconsole
project at https://github.com/bitstreamout/showconsole

Werner

-- 
  "Having a smoking section in a restaurant is like having
          a peeing section in a swimming pool." -- Edward Burr

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 894 bytes --]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Repost: [PATCH] Secure login and sulogin on S390x
  2026-05-05  9:39   ` Dr. Werner Fink
@ 2026-05-21  9:12     ` Dr. Werner Fink
  2026-05-26 16:00       ` Karel Zak
  0 siblings, 1 reply; 6+ messages in thread
From: Dr. Werner Fink @ 2026-05-21  9:12 UTC (permalink / raw)
  To: util-linux; +Cc: Karel Zak


[-- Attachment #1.1: Type: text/plain, Size: 253 bytes --]

Hi,

just to be sure that this change for s390x does not fall down behind.
Are there some changes required?

Werner

-- 
  "Having a smoking section in a restaurant is like having
          a peeing section in a swimming pool." -- Edward Burr

[-- Attachment #1.2: resend.patch --]
[-- Type: text/x-patch, Size: 21444 bytes --]

From e307759459f58a5d36d879bdf838e8319580a7bd Mon Sep 17 00:00:00 2001
From: Werner Fink <werner@suse.de>
Date: Thu, 21 May 2026 10:46:13 +0200
Subject: [PATCH] Secure login and sulogin on S390x

Some remarks: on S390x architecture of modern zSeries the hypervisor
does log the console I/O.  For both the 3215 half duplex line mode as
well as the 3270 full-screen/block mode console type the I/O is logged
on the hypervisor's side.  To control this there are command send via
/dev/vmcp to tell the  z/VM control program of the hypervisor not to
log during entering the password.  For the 3215 console also automatic
scroll is enabled which avoid to press CLEAR to get the password prompt
if on the next block.

Beside this for the ttysclp0, the serial ASCII terminal accessed via
a websocket has since ncurses 6.5-20250405 a new terminfo entry called
"sclp" to support not only the vt220 like behaviour but also the
colouring feature of ttysclp0.

Ensure to detect all special console case on S390x

Signed-off-by: Werner Fink <werner@suse.de>
---
 lib/ttyutils.c                 |   5 +-
 login-utils/Makemodule.am      |   9 +-
 login-utils/login.c            |  85 +++++++++++-
 login-utils/meson.build        |   5 +
 login-utils/sulogin-consoles.c |  44 ++++--
 login-utils/sulogin-consoles.h |   4 +
 login-utils/sulogin.c          |  64 +++++++--
 login-utils/vmcp.c             | 236 +++++++++++++++++++++++++++++++++
 login-utils/vmcp.h             |  29 ++++
 9 files changed, 460 insertions(+), 21 deletions(-)
 create mode 100644 login-utils/vmcp.c
 create mode 100644 login-utils/vmcp.h

diff --git a/lib/ttyutils.c b/lib/ttyutils.c
index bacd73077..3085419bd 100644
--- a/lib/ttyutils.c
+++ b/lib/ttyutils.c
@@ -21,7 +21,7 @@
 #  if defined (__s390__) || defined (__s390x__)
 #    define DEFAULT_TTYS0  "dumb"
 #    define DEFAULT_TTY32  "ibm327x"
-#    define DEFAULT_TTYS1  "vt220"
+#    define DEFAULT_TTYS1  "sclp"
 #  endif
 #  ifndef DEFAULT_STERM
 #    define DEFAULT_STERM  "vt102"
@@ -176,7 +176,8 @@ char *get_terminal_default_type(const char *ttyname, int is_serial)
 		 * Special terminal on first serial line on a S/390(x) which
 		 * is due legacy reasons a block terminal of type 3270 or
 		 * higher.  Whereas the second serial line on a S/390(x) is
-		 * a real character terminal which is compatible with VT220.
+		 * a real character terminal which is compatible with "sclp"
+		 * since ncurses 6.5-20250405 for e.g. color support.
 		 */
 		if (strcmp(ttyname, "ttyS0") == 0)		/* linux/drivers/s390/char/con3215.c */
 			return strdup(DEFAULT_TTYS0);
diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am
index a82e4b70f..4e3205577 100644
--- a/login-utils/Makemodule.am
+++ b/login-utils/Makemodule.am
@@ -33,7 +33,9 @@ dist_noinst_DATA += login-utils/sulogin.8.adoc
 sulogin_SOURCES = \
 	login-utils/sulogin.c \
 	login-utils/sulogin-consoles.c \
-	login-utils/sulogin-consoles.h
+	login-utils/sulogin-consoles.h \
+	login-utils/vmcp.c \
+	login-utils/vmcp.h
 if USE_PLYMOUTH_SUPPORT
 sulogin_SOURCES += lib/plymouth-ctrl.c
 endif
@@ -58,7 +60,10 @@ bin_PROGRAMS += login
 MANPAGES += login-utils/login.1
 dist_noinst_DATA += login-utils/login.1.adoc
 login_SOURCES = \
-	login-utils/login.c
+	login-utils/login.c \
+	login-utils/vmcp.c \
+	login-utils/vmcp.h \
+	lib/logindefs.c
 login_LDADD = $(LDADD) libcommon.la libcommon_logindefs.la libcommon_shells.la -lpam
 if HAVE_LINUXPAM
 login_LDADD += -lpam_misc
diff --git a/login-utils/login.c b/login-utils/login.c
index da119ee98..5730c449b 100644
--- a/login-utils/login.c
+++ b/login-utils/login.c
@@ -85,6 +85,7 @@
 #include "pwdutils.h"
 
 #include "logindefs.h"
+#include "vmcp.h"
 
 #if defined (HAVE_LIBECONF) && defined (USE_VENDORDIR)
 # include "shells.h"
@@ -165,6 +166,85 @@ static int is_consoletty(int fd)
 }
 #endif
 
+#if defined(__s390__) || defined(__s390x__)
+/* Avoid that passwords are logged on the hypervisors console log */
+
+#define CON_3215	0x0010	/* s390x 3215 halfduplex console */
+#define CON_3270	0x0020	/* s390x 3270 console */
+#define CON_SCLP	0x0040	/* s390x sclp terminals */
+
+struct s390con {
+	int flags;
+	int vmcpfd;
+};
+
+static struct s390con s390_console_spool_stop(int fd)
+{
+	struct stat stb;
+	struct s390con con = { .flags = 0, .vmcpfd = -1 };
+
+	if ((fstat(fd, &stb) >= 0)) {
+		if (major(stb.st_rdev) == 4 && minor(stb.st_rdev) == 64)
+			con.flags |= CON_3215;
+		if (major(stb.st_rdev) == 4 && minor(stb.st_rdev) >= 65)
+			con.flags |= CON_SCLP;
+		else if (major(stb.st_rdev) == 227 && minor(stb.st_rdev) >= 1)
+			con.flags |= CON_3270;
+	}
+	if (con.flags & (CON_3215|CON_3270)) {
+		con.vmcpfd = openvmcp();
+		if (con.vmcpfd >= 0) {
+			char *msg = queryspool(con.vmcpfd);
+			if (msg) {
+				parsespool(msg);
+				free(msg);
+			}
+			/*
+			 * Avoid that console log during
+			 * password input
+			 */
+			stopspool(con.vmcpfd);
+		}
+	}
+	if (con.flags & CON_3215) {
+		if (con.vmcpfd >= 0) {
+			char *msg = queryterm(con.vmcpfd);
+			if (msg) {
+				parseterm(msg);
+				free(msg);
+			}
+			/*
+			 * Avoid that CLEAR has to be pressed for
+			 * password input
+			 */
+			setterm(con.vmcpfd, "0");
+# ifdef WARN_WITH_VMCP
+			warning3215(con.vmcpfd);
+# endif
+		}
+	}
+	return con;
+}
+
+static void s390_console_spool_restore(struct s390con *con)
+{
+	if (con->vmcpfd >= 0) {
+		if (con->flags & CON_3215)
+			restoreterm(con->vmcpfd);
+		if (con->flags & (CON_3215|CON_3270))
+			restorespool(con->vmcpfd);
+		clearvmcp();
+		close(con->vmcpfd);
+		con->vmcpfd = -1;
+	}
+}
+# define PREPARE_CONSOLE(fd)	struct s390con con = s390_console_spool_stop(fd)
+# define RESTORE_CONSOLE()	s390_console_spool_restore(&con)
+#else
+# define PREPARE_CONSOLE(fd)	/* */
+# define RESTORE_CONSOLE()	/* */
+#endif
+
 /*
  * Robert Ambrose writes:
  * A couple of my users have a problem with login processes hanging around
@@ -1584,8 +1664,11 @@ int main(int argc, char **argv)
 	/* login -f, then the user has already been authenticated */
 	cxt.noauth = cxt.noauth && getuid() == 0 ? 1 : 0;
 
-	if (!cxt.noauth)
+	if (!cxt.noauth) {
+		PREPARE_CONSOLE(0);
 		loginpam_auth(&cxt);
+		RESTORE_CONSOLE();
+        }
 
 	/*
 	 * Authentication may be skipped (for example, during krlogin, rlogin,
diff --git a/login-utils/meson.build b/login-utils/meson.build
index 251d8de46..5f6310b01 100644
--- a/login-utils/meson.build
+++ b/login-utils/meson.build
@@ -49,6 +49,7 @@ test_islocal_sources = files(
 
 test_consoles_sources = files(
   'sulogin-consoles.c',
+  'vmcp.c',
 )
 
 last_sources = files(
@@ -59,6 +60,8 @@ last_manadocs = files('last.1.adoc')
 
 login_sources = files(
   'login.c',
+  'vmcp.c',
+  'vmcp.h',
 )
 login_manadocs = files('login.1.adoc')
 
@@ -66,6 +69,8 @@ sulogin_sources = files(
   'sulogin.c',
   'sulogin-consoles.c',
   'sulogin-consoles.h',
+  'vmcp.c',
+  'vmcp.h',
 )
 sulogin_manadocs = files('sulogin.8.adoc')
 
diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c
index c8156bfec..2d838f20e 100644
--- a/login-utils/sulogin-consoles.c
+++ b/login-utils/sulogin-consoles.c
@@ -312,7 +312,7 @@ static
 #ifdef __GNUC__
 __attribute__((__hot__))
 #endif
-int append_console(struct list_head *consoles, const char * const name)
+int append_console(struct list_head *consoles, const char * const name, dev_t dev)
 {
 	struct console *restrict tail;
 	const struct console *last = NULL;
@@ -343,7 +343,29 @@ int append_console(struct list_head *consoles, const char * const name)
 	tail->reset_tty_context = NULL;
 	tail->user_tty_context = NULL;
 #endif
-
+#if defined(__s390__) || defined(__s390x__)
+	/*
+	 * Stat the device path to determine its major/minor numbers. 
+	 * This ensures we detect s390x terminal types regardless of whether 
+	 * the console was found via /proc, /sys, cmdline, or a direct stdin 
+	 * fallback (e.g. sulogin < /dev/ttyS0).
+	 */
+	if (!dev) {
+		struct stat st;
+		if (stat(name, &st) == 0 && S_ISCHR(st.st_mode))
+			dev = st.st_rdev;
+	}
+	if (dev) {
+		unsigned int maj = major(dev);
+		unsigned int min = minor(dev);
+		if (maj == 4 && min == 64)
+			tail->flags |= CON_3215;
+		else if (maj == 4 && min >= 65)
+			tail->flags |= CON_SCLP;
+		else if (maj == 227 && min >= 1)
+			tail->flags |= CON_3270;
+	}
+#endif
 	return 0;
 }
 
@@ -385,10 +407,16 @@ static int detect_consoles_from_proc(struct list_head *consoles)
 		name = scandev(dir, comparedev);
 		if (!name)
 			continue;
-		rc = append_console(consoles, name);
+		rc = append_console(consoles, name, comparedev);
 		free(name);
 		if (rc < 0)
 			goto done;
+		if (!list_empty(consoles)) {
+			struct console *last;
+			last = list_last_entry(consoles, struct console, entry);
+			if (!strchr(fbuf, 'C'))
+				last->flags |= CON_CONSDEV;
+		}
 	}
 
 	rc = list_empty(consoles) ? 1 : 0;
@@ -447,7 +475,7 @@ static int detect_consoles_from_sysfs(struct list_head *consoles)
 		name = scandev(dir, comparedev);
 		if (!name)
 			continue;
-		rc = append_console(consoles, name);
+		rc = append_console(consoles, name, comparedev);
 		free(name);
 		if (rc < 0)
 			goto done;
@@ -535,7 +563,7 @@ static int detect_consoles_from_cmdline(struct list_head *consoles)
 		name = scandev(dir, comparedev);
 		if (!name)
 			continue;
-		rc = append_console(consoles, name);
+		rc = append_console(consoles, name, comparedev);
 		free(name);
 		if (rc < 0)
 			goto done;
@@ -593,7 +621,7 @@ static int detect_consoles_from_tiocgdev(struct list_head *consoles,
 			goto done;
 		}
 	}
-	rc = append_console(consoles, name);
+	rc = append_console(consoles, name, comparedev);
 	free(name);
 	if (rc < 0)
 		goto done;
@@ -707,7 +735,7 @@ int detect_consoles(const char *device, const int fallback, struct list_head *co
 		closedir(dir);
 
 		if (name) {
-			rc = append_console(consoles, name);
+			rc = append_console(consoles, name, comparedev);
 			free(name);
 			if (rc < 0)
 				return rc;
@@ -784,7 +812,7 @@ fallback:
 		n = strdup(name);
 		if (!n)
 			return -ENOMEM;
-		rc = append_console(consoles, n);
+		rc = append_console(consoles, n, 0);
 		free(n);
 		if (rc < 0)
 			return rc;
diff --git a/login-utils/sulogin-consoles.h b/login-utils/sulogin-consoles.h
index d3b4298e4..10fdf612c 100644
--- a/login-utils/sulogin-consoles.h
+++ b/login-utils/sulogin-consoles.h
@@ -39,6 +39,10 @@ struct console {
 #define	CON_SERIAL	0x0001
 #define	CON_NOTTY	0x0002
 #define	CON_EIO		0x0004
+#define	CON_CONSDEV	0x0008	/* Last on the kernel command line */
+#define	CON_3215	0x0010	/* s390x 3215 halfduplex console */
+#define	CON_3270	0x0020  /* s390x 3270 console */
+#define	CON_SCLP	0x0040	/* s390x sclp terminals */
 	pid_t pid;
 	struct chardata cp;
 	struct termios tio;
diff --git a/login-utils/sulogin.c b/login-utils/sulogin.c
index 23f5164e6..1984e6912 100644
--- a/login-utils/sulogin.c
+++ b/login-utils/sulogin.c
@@ -66,6 +66,7 @@
 #include "strutils.h"
 #include "ttyutils.h"
 #include "sulogin-consoles.h"
+#include "vmcp.h"
 #define CONMAX		16
 
 static unsigned int timeout;
@@ -218,7 +219,7 @@ static void tcinit(struct console *con)
 #endif
 
 #ifdef KDGKBMODE
-	if (!(con->flags & CON_SERIAL)
+	if (!(con->flags & (CON_SERIAL|CON_3215|CON_3270|CON_SCLP))
 	    && ioctl(fd, KDGKBMODE, &mode) < 0)
 		con->flags |= CON_SERIAL;
 	errno = 0;
@@ -265,6 +266,13 @@ static void tcinit(struct console *con)
 		}
 	}
 
+#if defined(__s390__) || defined(__s390x__)
+	if (con->flags & (CON_3215|CON_3270)) {
+		setlocale(LC_CTYPE, "POSIX");
+		setlocale(LC_MESSAGES, "POSIX");
+		goto setattr;
+	}
+#endif
 	/* Handle lines other than virtual consoles here */
 #if defined(KDGKBMODE) || defined(TIOCGSERIAL)
 	if (con->flags & CON_SERIAL)
@@ -292,7 +300,7 @@ static void tcinit(struct console *con)
 		cfsetospeed(tio, ospeed);
 
 #ifdef HAVE_STRUCT_TERMIOS_C_LINE
-		tio->c_line         = 0;
+		tio->c_line	 = 0;
 #endif
 		tio->c_cc[VTIME]    = 0;
 		tio->c_cc[VMIN]     = 1;
@@ -364,6 +372,10 @@ static void tcfinal(struct console *con)
 		free(term);
 	}
 
+#if defined(__s390__) || defined(__s390x__)
+	if (con->flags & (CON_3215|CON_3270))
+		return;
+#endif
 	if (!(con->flags & CON_SERIAL) || (con->flags & CON_NOTTY))
 		return;
 
@@ -983,9 +995,9 @@ static void usage(void)
 	fputs(_("Single-user login.\n"), out);
 
 	fputs(USAGE_OPTIONS, out);
-	fputs(_(" -p, --login-shell        start a login shell\n"
+	fputs(_(" -p, --login-shell	start a login shell\n"
 		" -t, --timeout <seconds>  max time to wait for a password (default: no limit)\n"
-		" -e, --force              examine password files directly if getpwnam(3) fails\n"),
+		" -e, --force	      examine password files directly if getpwnam(3) fails\n"),
 		out);
 
 	fputs(USAGE_SEPARATOR, out);
@@ -1012,8 +1024,8 @@ int main(int argc, char **argv)
 	static const struct option longopts[] = {
 		{ "login-shell",  no_argument,       NULL, 'p' },
 		{ "timeout",      required_argument, NULL, 't' },
-		{ "force",        no_argument,       NULL, 'e' },
-		{ "help",         no_argument,       NULL, 'h' },
+		{ "force",	no_argument,       NULL, 'e' },
+		{ "help",	 no_argument,       NULL, 'h' },
 		{ "version",      no_argument,       NULL, 'V' },
 		{ NULL, 0, NULL, 0 }
 	};
@@ -1193,10 +1205,46 @@ int main(int argc, char **argv)
 				char *answer;
 				int doshell = 0;
 				int deny = !opt_e && locked_account_password(pwd->pw_passwd);
-
+#if defined(__s390__) || defined(__s390x__)
+				int vmcpfd = -1;
+				if (con->flags & (CON_3215|CON_3270)) {
+					vmcpfd = openvmcp();
+					if (vmcpfd >= 0) {
+						char *msg = queryspool(vmcpfd);
+						if (msg) {
+							parsespool(msg);
+							free(msg);
+						}
+						stopspool(vmcpfd);
+					}
+				}
+				if (con->flags & CON_3215) {
+					if (vmcpfd >= 0) {
+						char *msg = queryterm(vmcpfd);
+						if (msg) {
+							parseterm(msg);
+							free(msg);
+						}
+						setterm(vmcpfd, "0");
+						warning3215(vmcpfd);
+					}
+				}
+#endif
 				doprompt(passwd, con, deny);
 
-				if ((answer = getpasswd(con)) == NULL)
+				answer = getpasswd(con);
+#if defined(__s390__) || defined(__s390x__)
+				if (vmcpfd >= 0) {
+					if (con->flags & CON_3215)
+						restoreterm(vmcpfd);
+					if (con->flags & (CON_3215|CON_3270))
+						restorespool(vmcpfd);
+					clearvmcp();
+					close(vmcpfd);
+					vmcpfd = -1;
+				}
+#endif
+				if (answer == NULL)
 					break;
 				if (deny) {
 #ifdef HAVE_EXPLICIT_BZERO
diff --git a/login-utils/vmcp.c b/login-utils/vmcp.c
new file mode 100644
index 000000000..52c2d4a6b
--- /dev/null
+++ b/login-utils/vmcp.c
@@ -0,0 +1,236 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * vmcp.c - s390x 3215 console spool and terminal management
+ *
+ * Copyright 2026 Werner Fink, SUSE Software Solutions Germany GmbH
+ *
+ * Based on:
+ *
+ * Copyright IBM Corp. 2018
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+int isinteger(const char *str)
+{
+    int errs = errno, ret = 1;
+    long __attribute__((__unused__)) val;
+    char *endptr;
+
+    errno = 0;
+    val = strtol(str, &endptr, 10);
+
+    /* Check if no digits were found at all */
+    if (str == endptr)
+	ret = 0;
+
+    /* Check for overflow or underflow */
+    if (errno == ERANGE)
+	ret = 0;
+
+    /* Check for trailing non-numeric characters (e.g., "123abc") */
+    if (*endptr != '\0')
+	ret = 0;
+
+    errno = errs;
+    return ret;
+}
+
+#if defined(__s390__) || defined(__s390x__)
+
+#define	VMCP_DEVICE_NODE	"/dev/vmcp"
+#define	VMCP_GETSIZE		_IOR(0x10, 3, int)
+#define	VMCP_SETBUF		_IOW(0x10, 2, int)
+#define	VMCP_GETCODE		_IOR(0x10, 1, int)
+
+static char *more, *hold;
+static int spooling;
+
+int openvmcp(void)
+{
+    return open(VMCP_DEVICE_NODE, O_RDWR|O_NOCTTY);
+}
+
+void clearvmcp(void)
+{
+    if (more) {
+	free(more);
+	more = NULL;
+    }
+    if (hold) {
+	free(hold);
+	hold = NULL;
+    }
+}
+
+static char* askvmcp(int fd, const char *question)
+{
+    long pagesize = sysconf(_SC_PAGESIZE);
+    int rc = 0, num, buffersize;
+    char* ret = NULL;
+
+    num = (strlen(question) + pagesize - 1)/pagesize;
+    buffersize = num * pagesize;
+
+    if (ioctl(fd, VMCP_SETBUF, &buffersize) == -1)
+	goto out;
+    do {
+	rc = write(fd, question, strlen(question));
+	if (rc < 0) {
+	    if (errno != EINTR)
+		goto out;
+	}
+    } while (rc < 0);
+    if (ioctl(fd, VMCP_GETCODE, &rc) == -1)
+	goto out;
+    if (ioctl(fd, VMCP_GETSIZE, &buffersize) == -1)
+	goto out;
+    ret = (char*)malloc(buffersize);
+    if (!ret)
+	goto out;
+    do {
+	rc = read(fd, ret, buffersize);
+	if (rc < 0) {
+	    if (errno != EINTR)
+		goto out;
+	}
+    } while (rc < 0);
+out:
+    return ret;
+}
+
+char* queryterm(int fd)
+{
+    const char* question = "QUERY TERMINAL";
+    return askvmcp(fd, question);
+}
+
+char* queryspool(int fd)
+{
+    const char* question = "QUERY VIRTUAL CONSOLE";
+    return askvmcp(fd, question);
+}
+
+static int writevmcp(int fd, char *instruction)
+{
+    long pagesize = sysconf(_SC_PAGESIZE);
+    int rc = 0, num, buffersize;
+    int ret = -1;
+
+    num = (strlen(instruction) + pagesize - 1)/pagesize;
+    buffersize = num * pagesize;
+
+    if (ioctl(fd, VMCP_SETBUF, &buffersize) == -1)
+	goto out;
+    do {
+	rc = write(fd, instruction, strlen(instruction));
+	if (rc < 0) {
+	    if (errno != EINTR)
+		goto out;
+	}
+    } while (rc < 0);
+    if (ioctl(fd, VMCP_GETCODE, &rc) == -1)
+	goto out;
+    if (ioctl(fd, VMCP_GETSIZE, &buffersize) == -1)
+	goto out;
+    if (rc == 0 && buffersize == 0)
+	ret = 0;
+out:
+    return ret;
+}
+
+int setterm(int fd, char *tout)
+{
+    char *instruction;
+    int ret = -1;
+
+    if (asprintf(&instruction, "TERMINAL MORE %s 0 HOLD OFF", tout) == -1)
+	goto out;
+
+    ret = writevmcp(fd, instruction);
+    free(instruction);
+out:
+    return ret;
+}
+
+int restoreterm(int fd)
+{
+    char* instruction;
+    int ret = -1;
+
+    if (!more || !hold)
+	goto out;
+    if (asprintf(&instruction, "TERMINAL %s %s", more, hold) == -1)
+	goto out;
+
+    ret = writevmcp(fd, instruction);
+    free(instruction);
+out:
+    return ret;
+}
+
+int stopspool(int fd)
+{
+    char* instruction = "SPOOL CONSOLE STOP";
+    if (!spooling)
+	return 1;
+    return writevmcp(fd, instruction);
+}
+
+int restorespool(int fd)
+{
+    char* instruction = "SPOOL CONSOLE START";
+    if (!spooling)
+	return 1;
+    return writevmcp(fd, instruction);
+}
+
+void parseterm(char *msg)
+{
+    int n;
+    char *token, *ptr;
+    for (n = 1, ptr = msg; ; n++, ptr = NULL) {
+	token = strtok(ptr, ",\n");
+	if (!token)
+	    break;
+	if (hold && more)
+	    break;
+	while (*token == ' ')
+	    token++;
+	if (strncmp("MORE ", token, 5) == 0)
+	    more = strdup(token);
+	if (strncmp("HOLD ", token, 5) == 0)
+	    hold = strdup(token);
+    }
+}
+
+void parsespool(char *msg)
+{
+    spooling = 0;
+    if (strstr(msg, " TERM START ") != NULL)
+	spooling = 1;
+}
+
+void warning3215(int fd)
+{
+    /*
+     * Warning: Do not translate this test as it might inlude then so called
+     * umlauts which in fact can not be encoded for the 3215 console interface.
+     * The 3215 console driver work in fact with EBCDIC codepage and the
+     * kernel has to translate such umlauts (multi or single bytes) with the
+     * correct EBCDIC character table (for german e.g. IBM-1141 or IBM-273).
+     */
+    (void) writevmcp(fd, "MESSAGE * WARNING: 3215 mode. Password visible!");
+    (void) writevmcp(fd, "MESSAGE * Ensure nobody is watching the screen.");
+}
+#endif
diff --git a/login-utils/vmcp.h b/login-utils/vmcp.h
new file mode 100644
index 000000000..50ef1ba90
--- /dev/null
+++ b/login-utils/vmcp.h
@@ -0,0 +1,29 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * vmcp.c - s390x 3215 console spool and terminal management
+ *
+ * Copyright 2026 Werner Fink, SUSE Software Solutions Germany GmbH
+ *
+ * Based on:
+ *
+ * Copyright IBM Corp. 2018
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ */
+
+extern int isinteger(const char *str);
+#if defined(__s390__) || defined(__s390x__)
+extern int openvmcp(void);
+extern void clearvmcp(void);
+extern char* queryterm(int fd);
+extern char* queryspool(int fd);
+extern int setterm(int fd, char *tout);
+extern int stopspool(int fd);
+extern int restoreterm(int fd);
+extern int restorespool(int fd);
+extern void parseterm(char *msg);
+extern void parsespool(char *msg);
+extern void warning3215(int fd);
+#endif
-- 
2.51.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 894 bytes --]

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: Repost: [PATCH] Secure login and sulogin on S390x
  2026-05-21  9:12     ` Repost: " Dr. Werner Fink
@ 2026-05-26 16:00       ` Karel Zak
  0 siblings, 0 replies; 6+ messages in thread
From: Karel Zak @ 2026-05-26 16:00 UTC (permalink / raw)
  To: util-linux, Karel Zak


Hi Werner,

On Thu, May 21, 2026 at 11:12:39AM +0200, Dr. Werner Fink wrote:
> just to be sure that this change for s390x does not fall down behind.
> Are there some changes required?

Sorry for the delay. I have pushed it to GitHub for CI to check:
https://github.com/util-linux/util-linux/pull/4325. I haven't had time
to work on it further.

    Karel


-- 
 Karel Zak  <kzak@redhat.com>
 http://karelzak.blogspot.com


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-05-26 16:00 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-29  9:28 [PATCH] Secure login and sulogin on S390x Werner Fink
2026-05-04 11:31 ` Dr. Werner Fink
2026-05-04 18:53 ` Chris Hofstaedtler
2026-05-05  9:39   ` Dr. Werner Fink
2026-05-21  9:12     ` Repost: " Dr. Werner Fink
2026-05-26 16:00       ` Karel Zak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox