The Pedigree Project 0.1
login.c
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <config.h>
21
22#define _GNU_SOURCE 1
23
24#include <ctype.h>
25#include <errno.h>
26#include <fcntl.h>
27#include <libintl.h>
28#include <locale.h>
29#include <pwd.h>
30#include <shadow.h>
31#include <signal.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <termios.h>
36#include <unistd.h>
37#include <utmp.h>
38#include <utmpx.h>
39
40#include <pedigree/log.h>
41#include <sys/ioctl.h>
42#include <sys/stat.h>
43#include <sys/wait.h>
44
45// PID of the process we're running
46int g_RunningPid = -1;
47
48// Pedigree function, from libpedigree-c
49extern int pedigree_login(int uid);
50
51// SIGINT handler
52void sigint(int sig) {
53 // If we're in the background...
54 if (g_RunningPid != -1) {
55 // Ignore, but don't log (running program)
56 } else {
57 // Do not kill us! CTRL-C does not do anything while the login prompt
58 // is active
59 pedigree_log(LOG_NOTICE, "SIGINT ignored");
60 }
61}
62
63static int password_matches(const char* password, const char* stored) {
64 if (!stored || stored[0] == '!' || stored[0] == '*')
65 return 0;
66 if (!stored[0])
67 return !password[0];
68 if (stored[0] == '$') {
69 const char* encrypted = crypt(password, stored);
70 return encrypted && !strcmp(encrypted, stored);
71 }
72
73 // The initial schema predates password hashing and stores plaintext values.
74 return !strcmp(password, stored);
75}
76
77int main(int argc, char** argv) {
78 setlocale(LC_ALL, "");
79 bindtextdomain("login", "/usr/share/locale");
80 bind_textdomain_codeset("login", "UTF-8");
81 textdomain("login");
82
83#ifdef INSTALLER
84 // For the installer, just run Python
85 printf("Loading installer, please wait...\n");
86
87 static const char* app_argv[] = {"/usr/bin/python", "/code/installer/install.py", 0};
88 static const char* app_env[] = {"TERM=xterm", "PATH=/usr/bin:/usr/sbin", "PYTHONHOME=/", 0};
89 execve("/usr/bin/python", (char* const*)app_argv, (char* const*)app_env);
90
91 printf("FATAL: Couldn't load Python!\n");
92
93 return 0;
94#endif
95
96 // New process group for job control. We'll ignore SIGINT for now.
97 signal(SIGINT, sigint);
98 setsid();
99
100 // Make sure we still have the terminal, though.
101 ioctl(1, TIOCSCTTY, 0);
102
103 // Set ourselves as the terminal's foreground process group.
104 tcsetpgrp(1, getpgrp());
105
106 // Get/fix $TERM.
107 const char* TERM = getenv("TERM");
108 if (!TERM) {
109 TERM = "pedigree";
110 setenv("TERM", TERM, 1);
111 }
112
113 const char* envLcAll = getenv("LC_ALL");
114 if (!envLcAll) {
115 envLcAll = "en_US.UTF-8";
116 setenv("LC_ALL", envLcAll, 1);
117 }
118
119 // Turn on output processing if it's not already on (we depend on it)
120 struct termios curt;
121 tcgetattr(1, &curt);
122 if (!(curt.c_oflag & OPOST))
123 curt.c_oflag |= OPOST;
124 tcsetattr(1, TCSANOW, &curt);
125
126 while (1) {
127 // Clear screen before from a previous session before we do anything
128 // else.
129 printf("\033[2J");
130
131 // Write the login greeting.
132 printf(gettext("Welcome to Pedigree\n"));
133
134 // Set terminal title, if we can.
135 if (!strcmp(TERM, "xterm")) {
136 printf("\033]0;");
137 printf(gettext("Pedigree Login"));
138 printf("\007");
139 }
140
141 // Not running anything
142 g_RunningPid = -1;
143
144 // This handles the case where a bad character goes into the stream and
145 // is impossible to get out. Everything else I've tried does not work...
146 int fd = open("/dev/tty", O_RDONLY);
147 if (fd < 0) {
148 pedigree_log(LOG_ERR, "Opening /dev/tty failed: %s", strerror(errno));
149 return 1;
150 }
151 if (fd != STDIN_FILENO) {
152 if (dup2(fd, STDIN_FILENO) < 0) {
153 pedigree_log(LOG_ERR, "Replacing stdin failed: %s", strerror(errno));
154 close(fd);
155 return 1;
156 }
157 close(fd);
158 }
159
160 // Get username
161 printf(gettext("Username: "));
162
163 char buffer[256];
164 char* username = NULL;
165
166 fflush(stdout);
167
168 username = fgets(buffer, 256, stdin);
169 if (!username) {
170 continue;
171 }
172
173 // Knock off the newline character
174 username[strlen(username) - 1] = '\0';
175 if (!strlen(username)) {
176 continue;
177 }
178
179 struct passwd* pw = getpwnam(username);
180 struct spwd* sp = getspnam(username);
181 if (!pw || !sp) {
182 printf(gettext("\nUnknown user: '%s'\n"), username);
183 continue;
184 }
185
186 // Get password
187 printf(gettext("Password: "));
188
189 char* password = NULL;
190
191 // Use own way - display *
192 fflush(stdout);
193 int c;
194 size_t i = 0;
195
196 tcgetattr(0, &curt);
197 curt.c_lflag &= ~(ECHO | ICANON);
198 tcsetattr(0, TCSANOW, &curt);
199 while ((c = getchar()) != '\n' && c != EOF) {
200 if (!c) {
201 continue;
202 } else if (c == '\b') {
203 if (i > 0) {
204 buffer[--i] = '\0';
205 printf("\b \b");
206 }
207 } else if (c != '\033' && i < (sizeof(buffer) - 1)) {
208 buffer[i++] = c;
209 if (!strcmp(TERM, "xterm")) {
210 printf("•");
211 } else {
212 printf("*");
213 }
214 }
215 }
216 tcgetattr(0, &curt);
217 curt.c_lflag |= (ECHO | ICANON);
218 tcsetattr(0, TCSANOW, &curt);
219 printf("\n");
220
221 buffer[i] = '\0';
222 password = buffer;
223
224 // Shadow entries in the initial image may contain plaintext passwords for
225 // compatibility with the existing configuration schema. Hashed entries
226 // use the standard crypt(3) format.
227 int passwordValid = password_matches(password, sp->sp_pwdp);
228
229 if (!passwordValid || pedigree_login(pw->pw_uid) != 0) {
230 printf(gettext("Password incorrect.\n"));
231 continue;
232 } else {
233 // Terminal title -> shell name.
234 if (!strcmp(TERM, "xterm"))
235 printf("\033]0;%s\007", pw->pw_shell);
236
237 // Successful login.
238 struct utmpx* p = 0;
239 setutxent();
240 do {
241 p = getutxent();
242 if (p && (p->ut_type == LOGIN_PROCESS && p->ut_pid == getpid()))
243 break;
244 } while (p);
245
246 if (p) {
247 struct utmpx ut;
248 memcpy(&ut, p, sizeof(ut));
249
250 struct timeval tv;
251 gettimeofday(&tv, NULL);
252 ut.ut_tv = tv;
253 ut.ut_type = USER_PROCESS;
254 strncpy(ut.ut_user, pw->pw_name, UT_NAMESIZE);
255
256 setutxent();
257 pututxline(&ut);
258 }
259 endutxent();
260
261 // Logged in successfully - launch the shell.
262 int pid;
263 pid = g_RunningPid = fork();
264
265 if (pid == -1) {
266 perror("fork");
267 exit(errno);
268 } else if (pid == 0) {
269 // Child...
270 g_RunningPid = -1;
271
272 // Environment - only pass certain variables to the new process.
273 char* newenv[4];
274 size_t homeSize = strlen(pw->pw_dir) + sizeof("HOME=");
275 size_t termSize = strlen(TERM) + sizeof("TERM=");
276 size_t localeSize = strlen(envLcAll) + sizeof("LC_ALL=");
277
278 newenv[0] = (char*)malloc(homeSize);
279 newenv[1] = (char*)malloc(termSize);
280 newenv[2] = (char*)malloc(localeSize);
281 newenv[3] = 0;
282
283 // Make sure we're starting a login shell.
284 size_t shellSize = strlen(pw->pw_shell) + 2;
285 char* shell = (char*)malloc(shellSize);
286
287 if (!newenv[0] || !newenv[1] || !newenv[2] || !shell) {
288 perror("malloc");
289 exit(1);
290 }
291
292 snprintf(newenv[0], homeSize, "HOME=%s", pw->pw_dir);
293 snprintf(newenv[1], termSize, "TERM=%s", TERM);
294 snprintf(newenv[2], localeSize, "LC_ALL=%s", envLcAll);
295 snprintf(shell, shellSize, "-%s", pw->pw_shell);
296
297 // Child.
298 execle(pw->pw_shell, shell, 0, newenv);
299
300 // If we got here, the exec failed.
301 perror("execve");
302 exit(1);
303 } else {
304 // Parent.
305 int stat;
306 waitpid(pid, &stat, 0);
307
308 g_RunningPid = -1;
309
310 continue;
311 }
312 }
313 }
314
315 return 0;
316}