Since no one has responded, I feel fine fixuping the patch, since I had a 
couple more changes. The segfault is still there, but I think I made it a 
little nicer. Anyway, here is the patch reborn.

Sent with [ProtonMail](https://protonmail.com) Secure Email.
From 267cfc47e57a49cd29551925fd22611f4ef231c7 Mon Sep 17 00:00:00 2001
From: Michael Christensen <[email protected]>
Date: Thu, 25 Mar 2021 16:42:17 -0600
Subject: [PATCH] Create (lsb) chsh command

---
 toys/lsb/chsh.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 150 insertions(+)
 create mode 100644 toys/lsb/chsh.c

diff --git a/toys/lsb/chsh.c b/toys/lsb/chsh.c
new file mode 100644
index 00000000..615727f2
--- /dev/null
+++ b/toys/lsb/chsh.c
@@ -0,0 +1,150 @@
+/* chsh.c - Change login shell.
+ *
+ * Copyright 2021 Michael Christensen
+ *
+ * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/chsh.html
+
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
+
+config CHSH
+  bool "chsh"
+  default y
+  help
+   usage: chsh [-s login_shell] [user]
+
+	The "change shell" command changes the user login shell.
+
+	-s login_shell    Use specified shell rather than interactive prompt
+
+    The shell must be an absolute path to an executable file. An unpriviliged
+    user can only change his/her own shell to one listed in /etc/shells, and
+    only if he/she is not restricted (implementation-defined).  */
+
+#define FOR_chsh
+#include "toys.h"
+
+GLOBALS(
+  char *s;
+)
+
+void chsh_main()
+{
+	int i;
+	FILE *file;
+	size_t size, buf_size;
+	char *user, *line, *shell, *password, *encrypted;
+	struct passwd *passwd_info;
+	struct spwd *shadow_info;
+
+	// Use max login name size for buffer size
+	if (-1 == (buf_size = sysconf(_SC_LOGIN_NAME_MAX))) buf_size = 256;
+
+	if (!(user = malloc(buf_size * sizeof(user))))
+        perror_exit("Failed to allocate memory");
+	if (!(shell = malloc(buf_size * sizeof(shell))))
+        perror_exit("Failed to allocate memory");
+	if (!(line = malloc(buf_size * sizeof(line))))
+        perror_exit("Failed to allocate memory");
+	if (MAP_FAILED == (password = mmap(NULL, buf_size, PROT_WRITE | PROT_READ,
+                    MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED | MAP_NORESERVE,
+                    -1, 0))) perror_exit("Failed to get memory map");
+
+	// Get uid user information, may be discarded later
+	if (!(passwd_info = getpwuid(getuid())))
+        perror_exit("Failed to get passwd record");
+
+	if ((user = *toys.optargs)) {
+		errno = 0;
+		if (!(passwd_info = getpwnam(user)) && !errno)
+            error_exit("Failed to get user info");
+
+		// Are we either root or changing our own shell?
+		if (getuid() && strcmp(passwd_info->pw_name, user))
+            error_exit("Permission denied\n");
+	} else user = passwd_info->pw_name;
+
+	// Get a password, encrypt it, wipe it, and check it
+	if (!(shadow_info = getspnam(passwd_info->pw_name)))
+        perror_exit("Failed to get shadow record");
+	if (read_password(password, buf_size, "Password: "))
+        error_exit("Failed to read password\n");
+	if (!(encrypted = crypt(password, shadow_info->sp_pwdp)))
+        perror_exit("Failed to encrypt password");
+	memset(password, 0, buf_size);
+	if (munmap(password, buf_size)) perror_exit("Failed to unmap memory");
+	if (strcmp(encrypted, shadow_info->sp_pwdp))
+        perror_exit("Incorrect password");
+
+	// Get new shell (either -s or interactive)
+	if (!(file = fopen("/etc/shells", "r")))
+        perror_exit("Failed to open /etc/shells");
+	if (toys.optflags) shell = TT.s;
+	else {
+		xprintf(
+"Changing the login shell for %s\n\
+Enter the new value, or press ENTER for default\n\
+    Login shell [%s]: ",
+                user, passwd_info->pw_shell);
+
+		errno = 0; size = 0;
+		while (EOF != (i = fgetc(stdin))) {
+			if (errno) perror_exit("Failed to read character from stdin");
+
+			if ('\n' != i) *(shell + size++) = i;
+			else {
+				*(shell + size) = '\0'; break;
+			}
+		}
+	}
+
+	// Is shell in /etc/shells?
+	if (strlen(shell)) {
+		line = NULL; size = 0; i = 0; errno = 0;
+
+		while (EOF != getline(&line, &size, file)) {
+			if (errno) perror_exit("Failed to read from /etc/shells");
+
+			size = strlen(line) - 1;
+			if ('\n' == *(line + size)) *(line + size) = '\0';
+
+			if (!strcmp(shell, line)) {
+				i = 1; break;
+			}
+		}
+
+		if (!i) error_exit("Shell not found in '/etc/shells'");
+	} else {
+
+		// Get default shell, ignoring comments and blank lines
+		do {
+			shell = NULL;
+			if (-1 == getline(&shell, &size, file))
+                perror_exit("Failed to read from /etc/shells");
+		} while (*shell != '/');
+
+		size = strlen(shell) - 1;
+		if ('\n' == *(shell + size)) *(shell + size) = '\0';
+	}
+
+	// Update shell and write passwd entry to tempfile
+    passwd_info->pw_shell = shell;
+	if (!(file = tmpfile())) perror_exit("Failed to create tempfile");
+	if (putpwent(passwd_info, file))
+        perror_exit("Failed to write to passwd entry");
+
+	// Move passwd entry from file to string
+	if (-1 == (i = ftell(file)))
+        perror_exit("Failed to get tempfile offset");
+	if (buf_size < i && !realloc(line, i))
+        perror_exit("Failed to reallocate memory");
+	rewind(file);
+	if (fread(line, 1, i, file) < i)
+        perror_exit("Failed to read from tempfile");
+    *strstr(line, "\n") = '\0';
+
+	// Update /etc/passwd using that string
+	if (-1 == update_password("/etc/passwd", passwd_info->pw_name, NULL))
+        perror_exit("Failed to remove passwd entry");
+	if (-1 == update_password("/etc/passwd", user, line))
+        perror_exit("Failed to add passwd entry");
+}
-- 
2.25.1

_______________________________________________
Toybox mailing list
[email protected]
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to