aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/chsh.c
blob: 1895755c11adb1716c180efd53a0e65c6d9045c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* 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\nEnter 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
	strncpy(passwd_info->pw_shell, shell, buf_size);
	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");

	// Update /etc/passwd using that string
	if (-1 == update_password("/etc/passwd", user, NULL)) perror_exit("Failed to remove passwd entry");
	if (-1 == update_password("/etc/passwd", user, line)) perror_exit("Failed to add passwd entry");
}