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 <AutoDeleter.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	FILE* tty = NULL;
29	if (!useStdio) {
30// TODO: Open tty with O_NOCTTY!
31		tty = fopen("/dev/tty", "w+");
32		if (tty == NULL) {
33			fprintf(stderr, "Error: Failed to open tty: %s\n", strerror(errno));
34			return errno;
35		}
36
37		in = tty;
38		out = tty;
39	}
40	CObjectDeleter<FILE, int> ttyCloser(tty, fclose);
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	fprintf(out, prompt);
64	fflush(out);
65
66	if (fgets(password, bufferSize, in) == NULL) {
67		fprintf(out, "\nError: Failed to read from tty: %s\n", strerror(errno));
68		error = errno != 0 ? errno : B_ERROR;
69	} else
70		fputc('\n', out);
71
72	// chop off trailing newline
73	if (error == B_OK) {
74		size_t len = strlen(password);
75		if (len > 0 && password[len - 1] == '\n')
76			password[len - 1] = '\0';
77	}
78
79	// restore the terminal attributes
80	termAttrs.c_lflag = localFlags;
81	tcsetattr(inFD, TCSANOW, &termAttrs);
82
83	return error;
84}
85
86
87bool
88verify_password(passwd* passwd, spwd* spwd, const char* plainPassword)
89{
90	if (passwd == NULL)
91		return false;
92
93	// check whether we need to check the shadow password
94	const char* requiredPassword = passwd->pw_passwd;
95	if (strcmp(requiredPassword, "x") == 0) {
96		if (spwd == NULL) {
97			// Mmh, we're suppose to check the shadow password, but we don't
98			// have it. Bail out.
99			return false;
100		}
101
102		requiredPassword = spwd->sp_pwdp;
103	}
104
105	// If no password is required, we're done.
106	if (requiredPassword == NULL || strlen(requiredPassword) == 0)
107		return true;
108
109	// crypt and check it
110	char* encryptedPassword = crypt(plainPassword, requiredPassword);
111
112	return (strcmp(encryptedPassword, requiredPassword) == 0);
113}
114
115
116/*!	Checks whether the user needs to authenticate with a password, and, if
117	necessary, asks for it, and checks it.
118	\a passwd must always be given, \a spwd only if there exists an entry
119	for the user.
120*/
121status_t
122authenticate_user(const char* prompt, passwd* passwd, spwd* spwd, int maxTries,
123	bool useStdio)
124{
125	// check whether a password is need at all
126	if (verify_password(passwd, spwd, ""))
127		return B_OK;
128
129	while (true) {
130		// prompt the user for the password
131		char plainPassword[MAX_SHADOW_PWD_PASSWORD_LEN];
132		status_t error = read_password(prompt, plainPassword,
133			sizeof(plainPassword), useStdio);
134		if (error != B_OK)
135			return error;
136
137		// check it
138		bool ok = verify_password(passwd, spwd, plainPassword);
139		memset(plainPassword, 0, sizeof(plainPassword));
140		if (ok)
141			return B_OK;
142
143		fprintf(stderr, "Incorrect password.\n");
144		if (--maxTries <= 0)
145			return B_PERMISSION_DENIED;
146	}
147}
148
149
150status_t
151authenticate_user(const char* prompt, const char* user, passwd** _passwd,
152	spwd** _spwd, int maxTries, bool useStdio)
153{
154	struct passwd* passwd = getpwnam(user);
155	struct spwd* spwd = getspnam(user);
156
157	status_t error = authenticate_user(prompt, passwd, spwd, maxTries,
158		useStdio);
159	if (error == B_OK) {
160		if (_passwd)
161			*_passwd = passwd;
162		if (_spwd)
163			*_spwd = spwd;
164	}
165
166	return error;
167}
168