1/*
2 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include "multiuser_utils.h"
7
8#include <errno.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <termios.h>
13#include <unistd.h>
14
15#include <AutoDeleterPosix.h>
16
17#include <user_group.h>
18
19
20status_t
21read_password(const char* prompt, char* password, size_t bufferSize,
22	bool useStdio)
23{
24	FILE* in = stdin;
25	FILE* out = stdout;
26
27	// open tty
28	FileCloser tty;
29	if (!useStdio) {
30// TODO: Open tty with O_NOCTTY!
31		tty.SetTo(fopen("/dev/tty", "w+"));
32		if (!tty.IsSet()) {
33			fprintf(stderr, "Error: Failed to open tty: %s\n",
34				strerror(errno));
35			return errno;
36		}
37
38		in = tty.Get();
39		out = tty.Get();
40	}
41
42	// disable echo
43	int inFD = fileno(in);
44	struct termios termAttrs;
45	if (tcgetattr(inFD, &termAttrs) != 0) {
46		fprintf(in, "Error: Failed to get tty attributes: %s\n",
47			strerror(errno));
48		return errno;
49	}
50
51	tcflag_t localFlags = termAttrs.c_lflag;
52	termAttrs.c_lflag &= ~ECHO;
53
54	if (tcsetattr(inFD, TCSANOW, &termAttrs) != 0) {
55		fprintf(in, "Error: Failed to set tty attributes: %s\n",
56			strerror(errno));
57		return errno;
58	}
59
60	status_t error = B_OK;
61
62	// prompt and read pwd
63	fputs(prompt, out);
64	fflush(out);
65
66	if (fgets(password, bufferSize, in) == NULL) {
67		fprintf(out, "\nError: Failed to read from tty: %s\n",
68			strerror(errno));
69		error = errno != 0 ? errno : B_ERROR;
70	} else
71		fputc('\n', out);
72
73	// chop off trailing newline
74	if (error == B_OK) {
75		size_t len = strlen(password);
76		if (len > 0 && password[len - 1] == '\n')
77			password[len - 1] = '\0';
78	}
79
80	// restore the terminal attributes
81	termAttrs.c_lflag = localFlags;
82	tcsetattr(inFD, TCSANOW, &termAttrs);
83
84	return error;
85}
86
87
88bool
89verify_password(passwd* passwd, spwd* spwd, const char* plainPassword)
90{
91	if (passwd == NULL)
92		return false;
93
94	// check whether we need to check the shadow password
95	const char* requiredPassword = passwd->pw_passwd;
96	if (strcmp(requiredPassword, "x") == 0) {
97		if (spwd == NULL) {
98			// Mmh, we're suppose to check the shadow password, but we don't
99			// have it. Bail out.
100			return false;
101		}
102
103		requiredPassword = spwd->sp_pwdp;
104	}
105
106	// If no password is required, we're done.
107	if (requiredPassword == NULL || requiredPassword[0] == '\0') {
108		if (plainPassword == NULL || plainPassword[0] == '\0')
109			return true;
110
111		return false;
112	}
113
114	// crypt and check it
115	char* encryptedPassword = crypt(plainPassword, requiredPassword);
116
117	return (strcmp(encryptedPassword, requiredPassword) == 0);
118}
119
120
121/*!	Checks whether the user needs to authenticate with a password, and, if
122	necessary, asks for it, and checks it.
123	\a passwd must always be given, \a spwd only if there exists an entry
124	for the user.
125*/
126status_t
127authenticate_user(const char* prompt, passwd* passwd, spwd* spwd, int maxTries,
128	bool useStdio)
129{
130	// check whether a password is need at all
131	if (verify_password(passwd, spwd, ""))
132		return B_OK;
133
134	while (true) {
135		// prompt the user for the password
136		char plainPassword[MAX_SHADOW_PWD_PASSWORD_LEN];
137		status_t error = read_password(prompt, plainPassword,
138			sizeof(plainPassword), useStdio);
139		if (error != B_OK)
140			return error;
141
142		// check it
143		bool ok = verify_password(passwd, spwd, plainPassword);
144		explicit_bzero(plainPassword, sizeof(plainPassword));
145		if (ok)
146			return B_OK;
147
148		fprintf(stderr, "Incorrect password.\n");
149		if (--maxTries <= 0)
150			return B_PERMISSION_DENIED;
151	}
152}
153
154
155status_t
156authenticate_user(const char* prompt, const char* user, passwd** _passwd,
157	spwd** _spwd, int maxTries, bool useStdio)
158{
159	struct passwd* passwd = getpwnam(user);
160	struct spwd* spwd = getspnam(user);
161
162	status_t error = authenticate_user(prompt, passwd, spwd, maxTries,
163		useStdio);
164	if (error == B_OK) {
165		if (_passwd)
166			*_passwd = passwd;
167		if (_spwd)
168			*_spwd = spwd;
169	}
170
171	return error;
172}
173
174
175status_t
176setup_environment(struct passwd* passwd, bool preserveEnvironment, bool chngdir)
177{
178	const char* term = getenv("TERM");
179	if (!preserveEnvironment) {
180		static char *empty[1];
181		environ = empty;
182	}
183
184	// always preserve $TERM
185	if (term != NULL)
186		setenv("TERM", term, false);
187	if (passwd->pw_shell)
188		setenv("SHELL", passwd->pw_shell, true);
189	if (passwd->pw_dir)
190		setenv("HOME", passwd->pw_dir, true);
191
192	setenv("USER", passwd->pw_name, true);
193
194	pid_t pid = getpid();
195	// If stdin is not open, don't bother trying to TIOCSPGRP. (This is the
196	// case when there is no PTY, e.g. for a noninteractive SSH session.)
197	if (fcntl(STDIN_FILENO, F_GETFD) != -1) {
198		if (ioctl(STDIN_FILENO, TIOCSPGRP, &pid) != 0)
199			return errno;
200	}
201
202	if (passwd->pw_gid && setgid(passwd->pw_gid) != 0)
203		return errno;
204
205	if (passwd->pw_uid && setuid(passwd->pw_uid) != 0)
206		return errno;
207
208	if (chngdir) {
209		const char* home = getenv("HOME");
210		if (home == NULL)
211			return B_ENTRY_NOT_FOUND;
212
213		if (chdir(home) != 0)
214			return errno;
215	}
216
217	return B_OK;
218}
219