]>
Commit | Line | Data |
---|---|---|
6dbe3af9 | 1 | /* |
0d28a157 KZ |
2 | * login(1) |
3 | * | |
4 | * This program is derived from 4.3 BSD software and is subject to the | |
5 | * copyright notice below. | |
6 | * | |
ee74f262 KZ |
7 | * Copyright (C) 2011 Karel Zak <kzak@redhat.com> |
8 | * Rewritten to PAM-only version. | |
9 | * | |
0d28a157 KZ |
10 | * Michael Glad (glad@daimi.dk) |
11 | * Computer Science Department, Aarhus University, Denmark | |
12 | * 1990-07-04 | |
13 | * | |
6dbe3af9 KZ |
14 | * Copyright (c) 1980, 1987, 1988 The Regents of the University of California. |
15 | * All rights reserved. | |
16 | * | |
17 | * Redistribution and use in source and binary forms are permitted | |
18 | * provided that the above copyright notice and this paragraph are | |
19 | * duplicated in all such forms and that any documentation, | |
20 | * advertising materials, and other materials related to such | |
21 | * distribution and use acknowledge that the software was developed | |
22 | * by the University of California, Berkeley. The name of the | |
23 | * University may not be used to endorse or promote products derived | |
24 | * from this software without specific prior written permission. | |
25 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR | |
26 | * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED | |
218b1dd6 | 27 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
6dbe3af9 | 28 | */ |
6dbe3af9 | 29 | #include <sys/param.h> |
fd6b7a7f | 30 | #include <stdio.h> |
6dbe3af9 KZ |
31 | #include <ctype.h> |
32 | #include <unistd.h> | |
33 | #include <getopt.h> | |
34 | #include <memory.h> | |
fd6b7a7f | 35 | #include <time.h> |
6dbe3af9 KZ |
36 | #include <sys/stat.h> |
37 | #include <sys/time.h> | |
38 | #include <sys/resource.h> | |
39 | #include <sys/file.h> | |
40 | #include <termios.h> | |
41 | #include <string.h> | |
6dbe3af9 | 42 | #include <sys/ioctl.h> |
fd6b7a7f | 43 | #include <sys/wait.h> |
6dbe3af9 KZ |
44 | #include <signal.h> |
45 | #include <errno.h> | |
46 | #include <grp.h> | |
47 | #include <pwd.h> | |
b4b919fe | 48 | #include <utmpx.h> |
7679a2c5 | 49 | |
6578ced7 RM |
50 | #ifdef HAVE_LASTLOG_H |
51 | # include <lastlog.h> | |
52 | #endif | |
7679a2c5 | 53 | |
6dbe3af9 | 54 | #include <stdlib.h> |
6dbe3af9 | 55 | #include <sys/syslog.h> |
7679a2c5 | 56 | |
cd8c4d03 TS |
57 | #ifdef HAVE_LINUX_MAJOR_H |
58 | # include <linux/major.h> | |
59 | #endif | |
7679a2c5 | 60 | |
6dbe3af9 | 61 | #include <netdb.h> |
905045d4 | 62 | #include <security/pam_appl.h> |
7679a2c5 | 63 | |
fe2c9909 WJ |
64 | #ifdef HAVE_SECURITY_PAM_MISC_H |
65 | # include <security/pam_misc.h> | |
66 | #elif defined(HAVE_SECURITY_OPENPAM_H) | |
67 | # include <security/openpam.h> | |
68 | #endif | |
ab71156c | 69 | |
f8bdba2f KZ |
70 | #ifdef HAVE_LIBAUDIT |
71 | # include <libaudit.h> | |
72 | #endif | |
0aeb57ac | 73 | |
cb5acd69 | 74 | #include "c.h" |
66ee8158 | 75 | #include "pathnames.h" |
8abcf290 | 76 | #include "strutils.h" |
7eda085c | 77 | #include "nls.h" |
984a6096 | 78 | #include "env.h" |
3e31a2df | 79 | #include "xalloc.h" |
e12c9866 | 80 | #include "all-io.h" |
5759de0c | 81 | #include "fileutils.h" |
3160589d | 82 | #include "timeutils.h" |
70aaa730 | 83 | #include "ttyutils.h" |
4f5f35fc | 84 | #include "pwdutils.h" |
6dbe3af9 | 85 | |
4d8fc09c KZ |
86 | #include "logindefs.h" |
87 | ||
48f09788 KZ |
88 | #define LOGIN_MAX_TRIES 3 |
89 | #define LOGIN_EXIT_TIMEOUT 5 | |
90 | #define LOGIN_TIMEOUT 60 | |
fd6b7a7f | 91 | |
b21d741c KZ |
92 | static char **argv0; |
93 | static size_t argv_lth; | |
94 | ||
99f7c131 KZ |
95 | #define VCS_PATH_MAX 64 |
96 | ||
5a528e2c KZ |
97 | #if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT) |
98 | # include <dirent.h> | |
5a528e2c KZ |
99 | # define MOTDDIR_SUPPORT |
100 | # define MOTDDIR_EXT ".motd" | |
101 | # define MOTDDIR_EXTSIZ (sizeof(MOTDDIR_EXT) - 1) | |
102 | #endif | |
103 | ||
99f7c131 KZ |
104 | /* |
105 | * Login control struct | |
106 | */ | |
107 | struct login_context { | |
108 | const char *tty_path; /* ttyname() return value */ | |
109 | const char *tty_name; /* tty_path without /dev prefix */ | |
110 | const char *tty_number; /* end of the tty_path */ | |
738246ed | 111 | mode_t tty_mode; /* chmod() mode */ |
99f7c131 | 112 | |
088d4876 KZ |
113 | const char *username; /* points to PAM, pwd or cmd_username */ |
114 | char *cmd_username; /* username specified on command line */ | |
115 | ||
67e70761 | 116 | struct passwd *pwd; /* user info */ |
4f5f35fc | 117 | char *pwdbuf; /* pwd strings */ |
67e70761 | 118 | |
eab72c4e KZ |
119 | pam_handle_t *pamh; /* PAM handler */ |
120 | struct pam_conv conv; /* PAM conversation */ | |
121 | ||
99f7c131 KZ |
122 | #ifdef LOGIN_CHOWN_VCS |
123 | char vcsn[VCS_PATH_MAX]; /* virtual console name */ | |
124 | char vcsan[VCS_PATH_MAX]; | |
125 | #endif | |
d20337ed | 126 | |
80591bf6 BS |
127 | char *thishost; /* this machine */ |
128 | char *thisdomain; /* this machine's domain */ | |
129 | char *hostname; /* remote machine */ | |
130 | char hostaddress[16]; /* remote address */ | |
0180264f KZ |
131 | |
132 | pid_t pid; | |
eab72c4e | 133 | |
111b395a KZ |
134 | unsigned int quiet:1, /* hush file exists */ |
135 | remote:1, /* login -h */ | |
92e386ca | 136 | nohost:1, /* login -H */ |
241e4565 KZ |
137 | noauth:1, /* login -f */ |
138 | keep_env:1; /* login -p */ | |
99f7c131 | 139 | }; |
726f69e2 | 140 | |
6dbe3af9 | 141 | /* |
80591bf6 | 142 | * This bounds the time given to login. Not a define, so it can |
6dbe3af9 KZ |
143 | * be patched on machines where it's too small. |
144 | */ | |
a169a454 | 145 | static int child_pid = 0; |
42ccc9cf | 146 | static volatile sig_atomic_t got_sig = 0; |
bfcba3f5 | 147 | static char *timeout_msg; |
6dbe3af9 | 148 | |
2f595c00 KZ |
149 | #ifdef LOGIN_CHOWN_VCS |
150 | /* true if the filedescriptor fd is a console tty, very Linux specific */ | |
151 | static int is_consoletty(int fd) | |
152 | { | |
153 | struct stat stb; | |
154 | ||
155 | if ((fstat(fd, &stb) >= 0) | |
156 | && (major(stb.st_rdev) == TTY_MAJOR) | |
157 | && (minor(stb.st_rdev) < 64)) { | |
158 | return 1; | |
159 | } | |
160 | return 0; | |
161 | } | |
162 | #endif | |
163 | ||
9abc9dab KZ |
164 | /* |
165 | * Robert Ambrose writes: | |
166 | * A couple of my users have a problem with login processes hanging around | |
167 | * soaking up pts's. What they seem to hung up on is trying to write out the | |
168 | * message 'Login timed out after %d seconds' when the connection has already | |
169 | * been dropped. | |
80591bf6 | 170 | * What I did was add a second timeout while trying to write the message, so |
9abc9dab KZ |
171 | * the process just exits if the second timeout expires. |
172 | */ | |
7679a2c5 SK |
173 | static void __attribute__((__noreturn__)) |
174 | timedout2(int sig __attribute__((__unused__))) | |
9abc9dab KZ |
175 | { |
176 | struct termios ti; | |
177 | ||
178 | /* reset echo */ | |
179 | tcgetattr(0, &ti); | |
180 | ti.c_lflag |= ECHO; | |
181 | tcsetattr(0, TCSANOW, &ti); | |
f17bda66 | 182 | _exit(EXIT_SUCCESS); /* %% */ |
9abc9dab KZ |
183 | } |
184 | ||
7679a2c5 | 185 | static void timedout(int sig __attribute__((__unused__))) |
9abc9dab KZ |
186 | { |
187 | signal(SIGALRM, timedout2); | |
188 | alarm(10); | |
bfcba3f5 SK |
189 | if (timeout_msg) |
190 | ignore_result( write(STDERR_FILENO, timeout_msg, strlen(timeout_msg)) ); | |
9abc9dab KZ |
191 | signal(SIGALRM, SIG_IGN); |
192 | alarm(0); | |
193 | timedout2(0); | |
194 | } | |
195 | ||
196 | /* | |
29e204d1 | 197 | * This handler can be used to inform a shell about signals to login. If you have |
80591bf6 BS |
198 | * (root) permissions, you can kill all login children by one signal to the |
199 | * login process. | |
9abc9dab | 200 | * |
80591bf6 BS |
201 | * Also, a parent who is session leader is able (before setsid() in the child) |
202 | * to inform the child when the controlling tty goes away (e.g. modem hangup). | |
9abc9dab KZ |
203 | */ |
204 | static void sig_handler(int signal) | |
205 | { | |
206 | if (child_pid) | |
207 | kill(-child_pid, signal); | |
208 | else | |
209 | got_sig = 1; | |
210 | if (signal == SIGTERM) | |
211 | kill(-child_pid, SIGHUP); /* because the shell often ignores SIGTERM */ | |
212 | } | |
213 | ||
f950752b | 214 | /* |
80591bf6 BS |
215 | * Let us delay all exit() calls when the user is not authenticated |
216 | * or the session not fully initialized (loginpam_session()). | |
f950752b | 217 | */ |
7679a2c5 | 218 | static void __attribute__((__noreturn__)) sleepexit(int eval) |
9abc9dab | 219 | { |
c9baf5da | 220 | sleep((unsigned int)getlogindefs_num("FAIL_DELAY", LOGIN_EXIT_TIMEOUT)); |
9abc9dab KZ |
221 | exit(eval); |
222 | } | |
223 | ||
7679a2c5 | 224 | static void process_title_init(int argc, char **argv) |
b21d741c KZ |
225 | { |
226 | int i; | |
227 | char **envp = environ; | |
228 | ||
229 | /* | |
230 | * Move the environment so we can reuse the memory. | |
231 | * (Code borrowed from sendmail.) | |
232 | * WARNING: ugly assumptions on memory layout here; | |
233 | * if this ever causes problems, #undef DO_PS_FIDDLING | |
234 | */ | |
235 | for (i = 0; envp[i] != NULL; i++) | |
236 | continue; | |
237 | ||
b04f1835 | 238 | environ = xmalloc(sizeof(char *) * (i + 1)); |
b21d741c KZ |
239 | |
240 | for (i = 0; envp[i] != NULL; i++) | |
b04f1835 | 241 | environ[i] = xstrdup(envp[i]); |
b21d741c KZ |
242 | environ[i] = NULL; |
243 | ||
244 | if (i > 0) | |
7679a2c5 | 245 | argv_lth = envp[i - 1] + strlen(envp[i - 1]) - argv[0]; |
b21d741c | 246 | else |
7679a2c5 | 247 | argv_lth = argv[argc - 1] + strlen(argv[argc - 1]) - argv[0]; |
b21d741c KZ |
248 | if (argv_lth > 1) |
249 | argv0 = argv; | |
250 | } | |
251 | ||
d5153819 | 252 | static void process_title_update(const char *username) |
b21d741c | 253 | { |
7679a2c5 SK |
254 | size_t i; |
255 | const char prefix[] = "login -- "; | |
256 | char buf[sizeof(prefix) + LOGIN_NAME_MAX]; | |
b21d741c | 257 | |
7679a2c5 SK |
258 | if (!argv0) |
259 | return; | |
b21d741c | 260 | |
d5153819 | 261 | if (sizeof(buf) < (sizeof(prefix) + strlen(username) + 1)) |
b21d741c KZ |
262 | return; |
263 | ||
d5153819 | 264 | snprintf(buf, sizeof(buf), "%s%s", prefix, username); |
b21d741c | 265 | |
7679a2c5 SK |
266 | i = strlen(buf); |
267 | if (i > argv_lth - 2) { | |
268 | i = argv_lth - 2; | |
269 | buf[i] = '\0'; | |
270 | } | |
271 | memset(argv0[0], '\0', argv_lth); /* clear the memory area */ | |
272 | strcpy(argv0[0], buf); | |
b21d741c | 273 | |
7679a2c5 | 274 | argv0[1] = NULL; |
b21d741c KZ |
275 | } |
276 | ||
92e386ca KZ |
277 | static const char *get_thishost(struct login_context *cxt, const char **domain) |
278 | { | |
f0196a13 KZ |
279 | if (!cxt->thishost) { |
280 | cxt->thishost = xgethostname(); | |
281 | if (!cxt->thishost) { | |
92e386ca KZ |
282 | if (domain) |
283 | *domain = NULL; | |
284 | return NULL; | |
285 | } | |
92e386ca KZ |
286 | cxt->thisdomain = strchr(cxt->thishost, '.'); |
287 | if (cxt->thisdomain) | |
288 | *cxt->thisdomain++ = '\0'; | |
289 | } | |
290 | ||
291 | if (domain) | |
292 | *domain = cxt->thisdomain; | |
293 | return cxt->thishost; | |
294 | } | |
295 | ||
5a528e2c KZ |
296 | #ifdef MOTDDIR_SUPPORT |
297 | static int motddir_filter(const struct dirent *d) | |
298 | { | |
299 | size_t namesz; | |
300 | ||
301 | #ifdef _DIRENT_HAVE_D_TYPE | |
302 | if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && | |
303 | d->d_type != DT_LNK) | |
304 | return 0; | |
305 | #endif | |
306 | if (*d->d_name == '.') | |
307 | return 0; | |
308 | ||
309 | namesz = strlen(d->d_name); | |
310 | if (!namesz || namesz < MOTDDIR_EXTSIZ + 1 || | |
311 | strcmp(d->d_name + (namesz - MOTDDIR_EXTSIZ), MOTDDIR_EXT) != 0) | |
312 | return 0; | |
313 | ||
314 | return 1; /* accept */ | |
315 | } | |
316 | ||
9789d21a | 317 | static int motddir(const char *dirname) |
5a528e2c | 318 | { |
7679a2c5 SK |
319 | int dd, nfiles, i, done = 0; |
320 | struct dirent **namelist = NULL; | |
5a528e2c | 321 | |
7679a2c5 | 322 | dd = open(dirname, O_RDONLY | O_CLOEXEC | O_DIRECTORY); |
5a528e2c | 323 | if (dd < 0) |
9789d21a | 324 | return 0; |
5a528e2c KZ |
325 | |
326 | nfiles = scandirat(dd, ".", &namelist, motddir_filter, versionsort); | |
327 | if (nfiles <= 0) | |
328 | goto done; | |
329 | ||
330 | for (i = 0; i < nfiles; i++) { | |
331 | struct dirent *d = namelist[i]; | |
332 | int fd; | |
333 | ||
7679a2c5 | 334 | fd = openat(dd, d->d_name, O_RDONLY | O_CLOEXEC); |
5a528e2c | 335 | if (fd >= 0) { |
8fcdbefb | 336 | ul_copy_file(fd, fileno(stdout)); |
5a528e2c | 337 | close(fd); |
9789d21a | 338 | done++; |
5a528e2c KZ |
339 | } |
340 | } | |
341 | ||
342 | for (i = 0; i < nfiles; i++) | |
343 | free(namelist[i]); | |
344 | free(namelist); | |
345 | done: | |
346 | close(dd); | |
9789d21a | 347 | return done; |
5a528e2c KZ |
348 | } |
349 | #endif /* MOTDDIR_SUPPORT */ | |
350 | ||
4d8fc09c | 351 | /* |
80591bf6 | 352 | * Output the /etc/motd file. |
4d8fc09c | 353 | * |
5a528e2c | 354 | * It determines the name of a login announcement file/dir and outputs it to the |
80591bf6 | 355 | * user's terminal at login time. The MOTD_FILE configuration option is a |
5a528e2c | 356 | * colon-delimited list of filenames or directories. An empty option disables |
80591bf6 | 357 | * message-of-the-day printing completely. |
4d8fc09c | 358 | */ |
9abc9dab KZ |
359 | static void motd(void) |
360 | { | |
4d8fc09c | 361 | const char *mb; |
5a528e2c | 362 | char *file, *list; |
9789d21a KZ |
363 | int firstonly, done = 0; |
364 | ||
365 | firstonly = getlogindefs_bool("MOTD_FIRSTONLY", 0); | |
9abc9dab | 366 | |
4d8fc09c KZ |
367 | mb = getlogindefs_str("MOTD_FILE", _PATH_MOTDFILE); |
368 | if (!mb || !*mb) | |
9abc9dab | 369 | return; |
4d8fc09c | 370 | |
5a528e2c | 371 | list = xstrdup(mb); |
34bb8eea | 372 | |
5a528e2c | 373 | for (file = strtok(list, ":"); file; file = strtok(NULL, ":")) { |
4d8fc09c | 374 | struct stat st; |
4d8fc09c | 375 | |
5a528e2c | 376 | if (stat(file, &st) < 0) |
4d8fc09c | 377 | continue; |
5a528e2c KZ |
378 | #ifdef MOTDDIR_SUPPORT |
379 | if (S_ISDIR(st.st_mode)) | |
9789d21a | 380 | done += motddir(file); |
5a528e2c KZ |
381 | #endif |
382 | if (S_ISREG(st.st_mode) && st.st_size > 0) { | |
383 | int fd = open(file, O_RDONLY, 0); | |
c60c65c3 | 384 | if (fd >= 0) { |
8fcdbefb | 385 | ul_copy_file(fd, fileno(stdout)); |
c60c65c3 SK |
386 | close(fd); |
387 | } | |
9789d21a | 388 | done++; |
5a528e2c | 389 | } |
9789d21a KZ |
390 | if (firstonly && done) |
391 | break; | |
4d8fc09c | 392 | } |
5a528e2c | 393 | free(list); |
9abc9dab | 394 | } |
99f7c131 | 395 | |
931e6098 SK |
396 | /* |
397 | * Display message of the day and you have mail notifications | |
398 | */ | |
96c82825 | 399 | static void display_login_messages(void) |
931e6098 SK |
400 | { |
401 | motd(); | |
402 | ||
403 | #ifdef LOGIN_STAT_MAIL | |
404 | /* | |
405 | * This turns out to be a bad idea: when the mail spool | |
406 | * is NFS mounted, and the NFS connection hangs, the | |
407 | * login hangs, even root cannot login. | |
408 | * Checking for mail should be done from the shell. | |
409 | */ | |
410 | { | |
411 | struct stat st; | |
412 | char *mail; | |
413 | ||
414 | mail = getenv("MAIL"); | |
415 | if (mail && stat(mail, &st) == 0 && st.st_size != 0) { | |
416 | if (st.st_mtime > st.st_atime) | |
417 | printf(_("You have new mail.\n")); | |
418 | else | |
419 | printf(_("You have mail.\n")); | |
420 | } | |
421 | } | |
422 | #endif | |
423 | } | |
424 | ||
99f7c131 | 425 | /* |
80591bf6 BS |
426 | * Nice and simple code provided by Linus Torvalds 16-Feb-93. |
427 | * Non-blocking stuff by Maciej W. Rozycki, macro@ds2.pg.gda.pl, 1999. | |
99f7c131 KZ |
428 | * |
429 | * He writes: "Login performs open() on a tty in a blocking mode. | |
430 | * In some cases it may make login wait in open() for carrier infinitely, | |
431 | * for example if the line is a simplistic case of a three-wire serial | |
80591bf6 | 432 | * connection. I believe login should open the line in non-blocking mode, |
99f7c131 | 433 | * leaving the decision to make a connection to getty (where it actually |
80591bf6 | 434 | * belongs)." |
99f7c131 KZ |
435 | */ |
436 | static void open_tty(const char *tty) | |
ab71156c | 437 | { |
1d4ad1de KZ |
438 | int i, fd, flags; |
439 | ||
440 | fd = open(tty, O_RDWR | O_NONBLOCK); | |
441 | if (fd == -1) { | |
960cf573 | 442 | syslog(LOG_ERR, _("FATAL: can't reopen tty: %m")); |
66020e56 | 443 | sleepexit(EXIT_FAILURE); |
1d4ad1de | 444 | } |
eb63b9b8 | 445 | |
8bee984a KZ |
446 | if (!isatty(fd)) { |
447 | close(fd); | |
448 | syslog(LOG_ERR, _("FATAL: %s is not a terminal"), tty); | |
66020e56 | 449 | sleepexit(EXIT_FAILURE); |
8bee984a KZ |
450 | } |
451 | ||
1d4ad1de KZ |
452 | flags = fcntl(fd, F_GETFL); |
453 | flags &= ~O_NONBLOCK; | |
454 | fcntl(fd, F_SETFL, flags); | |
e09947b5 | 455 | |
1d4ad1de KZ |
456 | for (i = 0; i < fd; i++) |
457 | close(i); | |
458 | for (i = 0; i < 3; i++) | |
459 | if (fd != i) | |
460 | dup2(fd, i); | |
461 | if (fd >= 3) | |
462 | close(fd); | |
6dbe3af9 KZ |
463 | } |
464 | ||
79a8afeb SK |
465 | static inline void chown_err(const char *what, uid_t uid, gid_t gid) |
466 | { | |
467 | syslog(LOG_ERR, _("chown (%s, %u, %u) failed: %m"), what, uid, gid); | |
468 | } | |
ff0392a0 | 469 | |
79a8afeb SK |
470 | static inline void chmod_err(const char *what, mode_t mode) |
471 | { | |
472 | syslog(LOG_ERR, _("chmod (%s, %u) failed: %m"), what, mode); | |
473 | } | |
ff0392a0 KZ |
474 | |
475 | static void chown_tty(struct login_context *cxt) | |
476 | { | |
45d0a30e | 477 | const char *grname; |
ff0392a0 KZ |
478 | uid_t uid = cxt->pwd->pw_uid; |
479 | gid_t gid = cxt->pwd->pw_gid; | |
480 | ||
45d0a30e KZ |
481 | grname = getlogindefs_str("TTYGROUP", TTYGRPNAME); |
482 | if (grname && *grname) { | |
10b3219a SK |
483 | struct group *gr = getgrnam(grname); |
484 | if (gr) /* group by name */ | |
485 | gid = gr->gr_gid; | |
486 | else /* group by ID */ | |
487 | gid = (gid_t) getlogindefs_num("TTYGROUP", gid); | |
45d0a30e | 488 | } |
ff0392a0 KZ |
489 | if (fchown(0, uid, gid)) /* tty */ |
490 | chown_err(cxt->tty_name, uid, gid); | |
738246ed KZ |
491 | if (fchmod(0, cxt->tty_mode)) |
492 | chmod_err(cxt->tty_name, cxt->tty_mode); | |
ff0392a0 KZ |
493 | |
494 | #ifdef LOGIN_CHOWN_VCS | |
495 | if (is_consoletty(0)) { | |
2f595c00 KZ |
496 | if (chown(cxt->vcsn, uid, gid)) /* vcs */ |
497 | chown_err(cxt->vcsn, uid, gid); | |
498 | if (chmod(cxt->vcsn, cxt->tty_mode)) | |
499 | chmod_err(cxt->vcsn, cxt->tty_mode); | |
500 | ||
80591bf6 | 501 | if (chown(cxt->vcsan, uid, gid)) /* vcsa */ |
2f595c00 KZ |
502 | chown_err(cxt->vcsan, uid, gid); |
503 | if (chmod(cxt->vcsan, cxt->tty_mode)) | |
504 | chmod_err(cxt->vcsan, cxt->tty_mode); | |
ff0392a0 KZ |
505 | } |
506 | #endif | |
507 | } | |
508 | ||
99f7c131 | 509 | /* |
9e930041 | 510 | * Reads the current terminal path and initializes cxt->tty_* variables. |
99f7c131 KZ |
511 | */ |
512 | static void init_tty(struct login_context *cxt) | |
ab71156c | 513 | { |
99f7c131 KZ |
514 | struct stat st; |
515 | struct termios tt, ttt; | |
7e58b71d | 516 | struct winsize ws; |
99f7c131 | 517 | |
738246ed KZ |
518 | cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE); |
519 | ||
285c1f3a | 520 | get_terminal_name(&cxt->tty_path, &cxt->tty_name, &cxt->tty_number); |
24f4bbff | 521 | |
99f7c131 KZ |
522 | /* |
523 | * In case login is suid it was possible to use a hardlink as stdin | |
524 | * and exploit races for a local root exploit. (Wojciech Purczynski). | |
525 | * | |
526 | * More precisely, the problem is ttyn := ttyname(0); ...; chown(ttyn); | |
527 | * here ttyname() might return "/tmp/x", a hardlink to a pseudotty. | |
7007991f | 528 | * All of this is a problem only when login is suid, which it isn't. |
99f7c131 KZ |
529 | */ |
530 | if (!cxt->tty_path || !*cxt->tty_path || | |
531 | lstat(cxt->tty_path, &st) != 0 || !S_ISCHR(st.st_mode) || | |
ad296391 | 532 | (st.st_nlink > 1 && strncmp(cxt->tty_path, "/dev/", 5) != 0) || |
99f7c131 | 533 | access(cxt->tty_path, R_OK | W_OK) != 0) { |
66020e56 | 534 | |
24f4bbff | 535 | syslog(LOG_ERR, _("FATAL: bad tty")); |
66020e56 | 536 | sleepexit(EXIT_FAILURE); |
24f4bbff | 537 | } |
99f7c131 | 538 | |
99f7c131 | 539 | #ifdef LOGIN_CHOWN_VCS |
d4cba553 KZ |
540 | if (cxt->tty_number) { |
541 | /* find names of Virtual Console devices, for later mode change */ | |
542 | snprintf(cxt->vcsn, sizeof(cxt->vcsn), "/dev/vcs%s", cxt->tty_number); | |
543 | snprintf(cxt->vcsan, sizeof(cxt->vcsan), "/dev/vcsa%s", cxt->tty_number); | |
544 | } | |
99f7c131 KZ |
545 | #endif |
546 | ||
7e58b71d DDM |
547 | /* The TTY size might be reset to 0x0 by the kernel when we close the stdin/stdout/stderr file |
548 | * descriptors so let's save the size now so we can reapply it later */ | |
549 | memset(&ws, 0, sizeof(struct winsize)); | |
550 | if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) | |
551 | syslog(LOG_WARNING, _("TIOCGWINSZ ioctl failed: %m")); | |
552 | ||
99f7c131 KZ |
553 | tcgetattr(0, &tt); |
554 | ttt = tt; | |
555 | ttt.c_cflag &= ~HUPCL; | |
556 | ||
738246ed | 557 | if ((fchown(0, 0, 0) || fchmod(0, cxt->tty_mode)) && errno != EROFS) { |
99f7c131 KZ |
558 | |
559 | syslog(LOG_ERR, _("FATAL: %s: change permissions failed: %m"), | |
560 | cxt->tty_path); | |
561 | sleepexit(EXIT_FAILURE); | |
562 | } | |
563 | ||
564 | /* Kill processes left on this tty */ | |
c5bb244e | 565 | tcsetattr(0, TCSANOW, &ttt); |
99f7c131 | 566 | |
2e703564 | 567 | /* |
9e930041 | 568 | * Let's close file descriptors before vhangup |
2e703564 KZ |
569 | * https://lkml.org/lkml/2012/6/5/145 |
570 | */ | |
571 | close(STDIN_FILENO); | |
572 | close(STDOUT_FILENO); | |
573 | close(STDERR_FILENO); | |
574 | ||
3fd1f771 | 575 | signal(SIGHUP, SIG_IGN); /* so vhangup() won't kill us */ |
99f7c131 KZ |
576 | vhangup(); |
577 | signal(SIGHUP, SIG_DFL); | |
578 | ||
579 | /* open stdin,stdout,stderr to the tty */ | |
580 | open_tty(cxt->tty_path); | |
581 | ||
582 | /* restore tty modes */ | |
583 | tcsetattr(0, TCSAFLUSH, &tt); | |
7e58b71d DDM |
584 | |
585 | /* Restore tty size */ | |
586 | if (ws.ws_row > 0 || ws.ws_col > 0) | |
587 | if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws) < 0) | |
588 | syslog(LOG_WARNING, _("TIOCSWINSZ ioctl failed: %m")); | |
24f4bbff KZ |
589 | } |
590 | ||
364cda48 | 591 | /* |
80591bf6 | 592 | * Logs failed login attempts in _PATH_BTMP, if it exists. |
364cda48 KZ |
593 | * Must be called only with username the name of an actual user. |
594 | * The most common login failure is to give password instead of username. | |
595 | */ | |
a2de6177 | 596 | static void log_btmp(struct login_context *cxt) |
ab71156c | 597 | { |
b4b919fe | 598 | struct utmpx ut; |
75d4dbb0 | 599 | struct timeval tv; |
364cda48 KZ |
600 | |
601 | memset(&ut, 0, sizeof(ut)); | |
602 | ||
ac5c12fd | 603 | str2memcpy(ut.ut_user, |
3eb8b797 | 604 | cxt->username ? cxt->username : "(unknown)", |
364cda48 KZ |
605 | sizeof(ut.ut_user)); |
606 | ||
d20337ed | 607 | if (cxt->tty_number) |
ac5c12fd | 608 | str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id)); |
d20337ed | 609 | if (cxt->tty_name) |
ac5c12fd | 610 | str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line)); |
364cda48 | 611 | |
75d4dbb0 KZ |
612 | gettimeofday(&tv, NULL); |
613 | ut.ut_tv.tv_sec = tv.tv_sec; | |
614 | ut.ut_tv.tv_usec = tv.tv_usec; | |
364cda48 | 615 | |
ab71156c | 616 | ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */ |
0180264f | 617 | ut.ut_pid = cxt->pid; |
c3f974a1 | 618 | |
d20337ed | 619 | if (cxt->hostname) { |
ac5c12fd | 620 | str2memcpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host)); |
0468860d | 621 | if (*cxt->hostaddress) |
d20337ed | 622 | memcpy(&ut.ut_addr_v6, cxt->hostaddress, |
ab71156c | 623 | sizeof(ut.ut_addr_v6)); |
364cda48 | 624 | } |
6e3bc8a6 | 625 | |
b4b919fe | 626 | updwtmpx(_PATH_BTMP, &ut); |
364cda48 | 627 | } |
b2452358 | 628 | |
f8bdba2f | 629 | #ifdef HAVE_LIBAUDIT |
67e70761 | 630 | static void log_audit(struct login_context *cxt, int status) |
f8bdba2f | 631 | { |
f8bdba2f | 632 | int audit_fd; |
67e70761 | 633 | struct passwd *pwd = cxt->pwd; |
f8bdba2f KZ |
634 | |
635 | audit_fd = audit_open(); | |
636 | if (audit_fd == -1) | |
637 | return; | |
c3f974a1 KZ |
638 | if (!pwd && cxt->username) |
639 | pwd = getpwnam(cxt->username); | |
640 | ||
641 | audit_log_acct_message(audit_fd, | |
642 | AUDIT_USER_LOGIN, | |
643 | NULL, | |
644 | "login", | |
645 | cxt->username ? cxt->username : "(unknown)", | |
7679a2c5 | 646 | pwd ? pwd->pw_uid : (unsigned int)-1, |
c3f974a1 KZ |
647 | cxt->hostname, |
648 | NULL, | |
649 | cxt->tty_name, | |
650 | status); | |
f8bdba2f KZ |
651 | |
652 | close(audit_fd); | |
653 | } | |
67e70761 KZ |
654 | #else /* !HAVE_LIBAUDIT */ |
655 | # define log_audit(cxt, status) | |
ab71156c | 656 | #endif /* HAVE_LIBAUDIT */ |
f8bdba2f | 657 | |
3761d0bb KZ |
658 | static void log_lastlog(struct login_context *cxt) |
659 | { | |
6f7eba20 | 660 | struct sigaction sa, oldsa_xfsz; |
3761d0bb | 661 | struct lastlog ll; |
370734a7 | 662 | off_t offset; |
3761d0bb KZ |
663 | time_t t; |
664 | int fd; | |
665 | ||
67e70761 KZ |
666 | if (!cxt->pwd) |
667 | return; | |
668 | ||
1a83c00d KZ |
669 | if (cxt->pwd->pw_uid > (uid_t) getlogindefs_num("LASTLOG_UID_MAX", ULONG_MAX)) |
670 | return; | |
671 | ||
6f7eba20 KZ |
672 | /* lastlog is huge on systems with large UIDs, ignore SIGXFSZ */ |
673 | memset(&sa, 0, sizeof(sa)); | |
674 | sa.sa_handler = SIG_IGN; | |
675 | sigaction(SIGXFSZ, &sa, &oldsa_xfsz); | |
676 | ||
3761d0bb KZ |
677 | fd = open(_PATH_LASTLOG, O_RDWR, 0); |
678 | if (fd < 0) | |
6f7eba20 | 679 | goto done; |
370734a7 | 680 | offset = cxt->pwd->pw_uid * sizeof(ll); |
3761d0bb KZ |
681 | |
682 | /* | |
80591bf6 | 683 | * Print last log message. |
3761d0bb KZ |
684 | */ |
685 | if (!cxt->quiet) { | |
370734a7 SK |
686 | if ((pread(fd, (void *)&ll, sizeof(ll), offset) == sizeof(ll)) && |
687 | ll.ll_time != 0) { | |
3160589d | 688 | char time_string[CTIME_BUFSIZ]; |
b648917e | 689 | char buf[sizeof(ll.ll_host) + 1]; |
3160589d | 690 | |
7679a2c5 | 691 | time_t ll_time = (time_t)ll.ll_time; |
3761d0bb | 692 | |
3160589d SK |
693 | ctime_r(&ll_time, time_string); |
694 | printf(_("Last login: %.*s "), 24 - 5, time_string); | |
b648917e KZ |
695 | |
696 | if (*ll.ll_host != '\0') { | |
697 | mem2strcpy(buf, ll.ll_host, sizeof(ll.ll_host), sizeof(buf)); | |
698 | printf(_("from %s\n"), buf); | |
699 | } else { | |
700 | mem2strcpy(buf, ll.ll_line, sizeof(ll.ll_line), sizeof(buf)); | |
701 | printf(_("on %s\n"), buf); | |
702 | } | |
3761d0bb | 703 | } |
3761d0bb KZ |
704 | } |
705 | ||
706 | memset((char *)&ll, 0, sizeof(ll)); | |
707 | ||
708 | time(&t); | |
709 | ll.ll_time = t; /* ll_time is always 32bit */ | |
710 | ||
711 | if (cxt->tty_name) | |
ac5c12fd | 712 | str2memcpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line)); |
3761d0bb | 713 | if (cxt->hostname) |
ac5c12fd | 714 | str2memcpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host)); |
3761d0bb | 715 | |
370734a7 | 716 | if (pwrite(fd, (void *)&ll, sizeof(ll), offset) != sizeof(ll)) |
3761d0bb | 717 | warn(_("write lastlog failed")); |
7488d4c0 | 718 | done: |
6f7eba20 KZ |
719 | if (fd >= 0) |
720 | close(fd); | |
721 | ||
722 | sigaction(SIGXFSZ, &oldsa_xfsz, NULL); /* restore original setting */ | |
3761d0bb KZ |
723 | } |
724 | ||
6e3bc8a6 | 725 | /* |
80591bf6 | 726 | * Update wtmp and utmp logs. |
6e3bc8a6 KZ |
727 | */ |
728 | static void log_utmp(struct login_context *cxt) | |
729 | { | |
7679a2c5 | 730 | struct utmpx ut = { 0 }; |
d2ab69ff | 731 | struct utmpx *utp = NULL; |
7679a2c5 | 732 | struct timeval tv = { 0 }; |
6e3bc8a6 | 733 | |
b4b919fe RM |
734 | utmpxname(_PATH_UTMP); |
735 | setutxent(); | |
6e3bc8a6 KZ |
736 | |
737 | /* Find pid in utmp. | |
738 | * | |
739 | * login sometimes overwrites the runlevel entry in /var/run/utmp, | |
740 | * confusing sysvinit. I added a test for the entry type, and the | |
741 | * problem was gone. (In a runlevel entry, st_pid is not really a pid | |
80591bf6 | 742 | * but some number calculated from the previous and current runlevel.) |
6e3bc8a6 KZ |
743 | * -- Michael Riepe <michael@stud.uni-hannover.de> |
744 | */ | |
b4b919fe | 745 | while ((utp = getutxent())) |
6e3bc8a6 KZ |
746 | if (utp->ut_pid == cxt->pid |
747 | && utp->ut_type >= INIT_PROCESS | |
748 | && utp->ut_type <= DEAD_PROCESS) | |
749 | break; | |
750 | ||
751 | /* If we can't find a pre-existing entry by pid, try by line. | |
80591bf6 | 752 | * BSD network daemons may rely on this. */ |
98193daa | 753 | if (utp == NULL && cxt->tty_name) { |
b4b919fe | 754 | setutxent(); |
6e3bc8a6 | 755 | ut.ut_type = LOGIN_PROCESS; |
ac5c12fd | 756 | str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line)); |
b4b919fe | 757 | utp = getutxline(&ut); |
6e3bc8a6 KZ |
758 | } |
759 | ||
98193daa | 760 | /* If we can't find a pre-existing entry by pid and line, try it by id. |
80591bf6 | 761 | * Very stupid telnetd daemons don't set up utmp at all. (kzak) */ |
98193daa | 762 | if (utp == NULL && cxt->tty_number) { |
7679a2c5 SK |
763 | setutxent(); |
764 | ut.ut_type = DEAD_PROCESS; | |
765 | str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id)); | |
766 | utp = getutxid(&ut); | |
98193daa KZ |
767 | } |
768 | ||
6e3bc8a6 KZ |
769 | if (utp) |
770 | memcpy(&ut, utp, sizeof(ut)); | |
771 | else | |
772 | /* some gettys/telnetds don't initialize utmp... */ | |
773 | memset(&ut, 0, sizeof(ut)); | |
774 | ||
d4cba553 | 775 | if (cxt->tty_number && ut.ut_id[0] == 0) |
ac5c12fd | 776 | str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id)); |
d4cba553 | 777 | if (cxt->username) |
ac5c12fd | 778 | str2memcpy(ut.ut_user, cxt->username, sizeof(ut.ut_user)); |
d4cba553 | 779 | if (cxt->tty_name) |
ac5c12fd | 780 | str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line)); |
6e3bc8a6 | 781 | |
6e3bc8a6 KZ |
782 | gettimeofday(&tv, NULL); |
783 | ut.ut_tv.tv_sec = tv.tv_sec; | |
784 | ut.ut_tv.tv_usec = tv.tv_usec; | |
6e3bc8a6 KZ |
785 | ut.ut_type = USER_PROCESS; |
786 | ut.ut_pid = cxt->pid; | |
787 | if (cxt->hostname) { | |
ac5c12fd | 788 | str2memcpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host)); |
0468860d | 789 | if (*cxt->hostaddress) |
6e3bc8a6 KZ |
790 | memcpy(&ut.ut_addr_v6, cxt->hostaddress, |
791 | sizeof(ut.ut_addr_v6)); | |
792 | } | |
793 | ||
b4b919fe RM |
794 | pututxline(&ut); |
795 | endutxent(); | |
6e3bc8a6 | 796 | |
b4b919fe | 797 | updwtmpx(_PATH_WTMP, &ut); |
6e3bc8a6 KZ |
798 | } |
799 | ||
516b00c4 KZ |
800 | static void log_syslog(struct login_context *cxt) |
801 | { | |
802 | struct passwd *pwd = cxt->pwd; | |
803 | ||
d4cba553 KZ |
804 | if (!cxt->tty_name) |
805 | return; | |
806 | ||
516b00c4 KZ |
807 | if (!strncmp(cxt->tty_name, "ttyS", 4)) |
808 | syslog(LOG_INFO, _("DIALUP AT %s BY %s"), | |
809 | cxt->tty_name, pwd->pw_name); | |
810 | ||
811 | if (!pwd->pw_uid) { | |
812 | if (cxt->hostname) | |
813 | syslog(LOG_NOTICE, _("ROOT LOGIN ON %s FROM %s"), | |
814 | cxt->tty_name, cxt->hostname); | |
815 | else | |
816 | syslog(LOG_NOTICE, _("ROOT LOGIN ON %s"), cxt->tty_name); | |
817 | } else { | |
818 | if (cxt->hostname) | |
819 | syslog(LOG_INFO, _("LOGIN ON %s BY %s FROM %s"), | |
820 | cxt->tty_name, pwd->pw_name, cxt->hostname); | |
821 | else | |
822 | syslog(LOG_INFO, _("LOGIN ON %s BY %s"), cxt->tty_name, | |
823 | pwd->pw_name); | |
824 | } | |
825 | } | |
826 | ||
53e9eda3 | 827 | /* encapsulate stupid "void **" pam_get_item() API */ |
088d4876 | 828 | static int loginpam_get_username(pam_handle_t *pamh, const char **name) |
53e9eda3 | 829 | { |
088d4876 | 830 | const void *item = (const void *)*name; |
53e9eda3 | 831 | int rc; |
7679a2c5 | 832 | |
53e9eda3 | 833 | rc = pam_get_item(pamh, PAM_USER, &item); |
088d4876 | 834 | *name = (const char *)item; |
53e9eda3 KZ |
835 | return rc; |
836 | } | |
53e9eda3 | 837 | |
a40f08ef | 838 | static void loginpam_err(pam_handle_t *pamh, int retcode) |
905045d4 KZ |
839 | { |
840 | const char *msg = pam_strerror(pamh, retcode); | |
841 | ||
842 | if (msg) { | |
843 | fprintf(stderr, "\n%s\n", msg); | |
844 | syslog(LOG_ERR, "%s", msg); | |
845 | } | |
846 | pam_end(pamh, retcode); | |
f950752b | 847 | sleepexit(EXIT_FAILURE); |
905045d4 KZ |
848 | } |
849 | ||
92e386ca | 850 | /* |
e6b32e7d KZ |
851 | * Composes "<host> login: " string; or returns "login: " if -H is given or |
852 | * LOGIN_PLAIN_PROMPT=yes configured. | |
92e386ca KZ |
853 | */ |
854 | static const char *loginpam_get_prompt(struct login_context *cxt) | |
855 | { | |
856 | const char *host; | |
857 | char *prompt, *dflt_prompt = _("login: "); | |
858 | size_t sz; | |
859 | ||
e6b32e7d KZ |
860 | if (cxt->nohost) |
861 | return dflt_prompt; /* -H on command line */ | |
862 | ||
863 | if (getlogindefs_bool("LOGIN_PLAIN_PROMPT", 0) == 1) | |
92e386ca KZ |
864 | return dflt_prompt; |
865 | ||
e6b32e7d KZ |
866 | if (!(host = get_thishost(cxt, NULL))) |
867 | return dflt_prompt; | |
92e386ca | 868 | |
e6b32e7d | 869 | sz = strlen(host) + 1 + strlen(dflt_prompt) + 1; |
92e386ca KZ |
870 | prompt = xmalloc(sz); |
871 | snprintf(prompt, sz, "%s %s", host, dflt_prompt); | |
872 | ||
873 | return prompt; | |
874 | } | |
875 | ||
79a8afeb SK |
876 | static inline int is_pam_failure(int rc) |
877 | { | |
878 | return rc != PAM_SUCCESS; | |
879 | } | |
880 | ||
eab72c4e KZ |
881 | static pam_handle_t *init_loginpam(struct login_context *cxt) |
882 | { | |
883 | pam_handle_t *pamh = NULL; | |
884 | int rc; | |
885 | ||
886 | /* | |
887 | * username is initialized to NULL and if specified on the command line | |
80591bf6 | 888 | * it is set. Therefore, we are safe not setting it to anything. |
eab72c4e KZ |
889 | */ |
890 | rc = pam_start(cxt->remote ? "remote" : "login", | |
891 | cxt->username, &cxt->conv, &pamh); | |
892 | if (rc != PAM_SUCCESS) { | |
893 | warnx(_("PAM failure, aborting: %s"), pam_strerror(pamh, rc)); | |
894 | syslog(LOG_ERR, _("Couldn't initialize PAM: %s"), | |
895 | pam_strerror(pamh, rc)); | |
f950752b | 896 | sleepexit(EXIT_FAILURE); |
eab72c4e KZ |
897 | } |
898 | ||
899 | /* hostname & tty are either set to NULL or their correct values, | |
80591bf6 | 900 | * depending on how much we know. */ |
eab72c4e KZ |
901 | rc = pam_set_item(pamh, PAM_RHOST, cxt->hostname); |
902 | if (is_pam_failure(rc)) | |
903 | loginpam_err(pamh, rc); | |
904 | ||
9c7a613c KZ |
905 | if (cxt->tty_path) { |
906 | rc = pam_set_item(pamh, PAM_TTY, cxt->tty_path); | |
907 | if (is_pam_failure(rc)) | |
908 | loginpam_err(pamh, rc); | |
909 | } | |
eab72c4e KZ |
910 | |
911 | /* | |
912 | * Andrew.Taylor@cal.montage.ca: Provide a user prompt to PAM so that | |
913 | * the "login: " prompt gets localized. Unfortunately, PAM doesn't have | |
914 | * an interface to specify the "Password: " string (yet). | |
915 | */ | |
92e386ca | 916 | rc = pam_set_item(pamh, PAM_USER_PROMPT, loginpam_get_prompt(cxt)); |
eab72c4e KZ |
917 | if (is_pam_failure(rc)) |
918 | loginpam_err(pamh, rc); | |
919 | ||
80591bf6 | 920 | /* We don't need the original username. We have to follow PAM. */ |
eab72c4e KZ |
921 | cxt->username = NULL; |
922 | cxt->pamh = pamh; | |
923 | ||
924 | return pamh; | |
925 | } | |
926 | ||
a2de6177 KZ |
927 | static void loginpam_auth(struct login_context *cxt) |
928 | { | |
df09e21c | 929 | int rc, show_unknown, keep_username; |
7d18972b | 930 | unsigned int retries, failcount = 0; |
cea8ec53 KZ |
931 | const char *hostname = cxt->hostname ? cxt->hostname : |
932 | cxt->tty_name ? cxt->tty_name : "<unknown>"; | |
a2de6177 KZ |
933 | pam_handle_t *pamh = cxt->pamh; |
934 | ||
935 | /* if we didn't get a user on the command line, set it to NULL */ | |
936 | loginpam_get_username(pamh, &cxt->username); | |
937 | ||
cea8ec53 | 938 | show_unknown = getlogindefs_bool("LOG_UNKFAIL_ENAB", 0); |
fab1f671 | 939 | retries = getlogindefs_num("LOGIN_RETRIES", LOGIN_MAX_TRIES); |
df09e21c | 940 | keep_username = getlogindefs_bool("LOGIN_KEEP_USERNAME", 0); |
cea8ec53 | 941 | |
a2de6177 KZ |
942 | /* |
943 | * There may be better ways to deal with some of these conditions, but | |
944 | * at least this way I don't think we'll be giving away information... | |
945 | * | |
946 | * Perhaps someday we can trust that all PAM modules will pay attention | |
fab1f671 | 947 | * to failure count and get rid of LOGIN_MAX_TRIES? |
a2de6177 KZ |
948 | */ |
949 | rc = pam_authenticate(pamh, 0); | |
950 | ||
fab1f671 | 951 | while ((++failcount < retries) && |
a2de6177 KZ |
952 | ((rc == PAM_AUTH_ERR) || |
953 | (rc == PAM_USER_UNKNOWN) || | |
954 | (rc == PAM_CRED_INSUFFICIENT) || | |
955 | (rc == PAM_AUTHINFO_UNAVAIL))) { | |
956 | ||
cea8ec53 KZ |
957 | if (rc == PAM_USER_UNKNOWN && !show_unknown) |
958 | /* | |
80591bf6 | 959 | * Logging unknown usernames may be a security issue if |
c9c7de79 | 960 | * a user enters their password instead of their login name. |
cea8ec53 KZ |
961 | */ |
962 | cxt->username = NULL; | |
963 | else | |
964 | loginpam_get_username(pamh, &cxt->username); | |
a2de6177 KZ |
965 | |
966 | syslog(LOG_NOTICE, | |
7d18972b | 967 | _("FAILED LOGIN %u FROM %s FOR %s, %s"), |
cea8ec53 KZ |
968 | failcount, hostname, |
969 | cxt->username ? cxt->username : "(unknown)", | |
a2de6177 KZ |
970 | pam_strerror(pamh, rc)); |
971 | ||
972 | log_btmp(cxt); | |
973 | log_audit(cxt, 0); | |
974 | ||
df09e21c TM |
975 | if (!keep_username || rc == PAM_USER_UNKNOWN) { |
976 | pam_set_item(pamh, PAM_USER, NULL); | |
977 | fprintf(stderr, _("Login incorrect\n\n")); | |
978 | } else | |
979 | fprintf(stderr, _("Password incorrect\n\n")); | |
980 | ||
a2de6177 KZ |
981 | rc = pam_authenticate(pamh, 0); |
982 | } | |
983 | ||
984 | if (is_pam_failure(rc)) { | |
985 | ||
cea8ec53 KZ |
986 | if (rc == PAM_USER_UNKNOWN && !show_unknown) |
987 | cxt->username = NULL; | |
988 | else | |
989 | loginpam_get_username(pamh, &cxt->username); | |
a2de6177 KZ |
990 | |
991 | if (rc == PAM_MAXTRIES) | |
992 | syslog(LOG_NOTICE, | |
7d18972b | 993 | _("TOO MANY LOGIN TRIES (%u) FROM %s FOR %s, %s"), |
cea8ec53 KZ |
994 | failcount, hostname, |
995 | cxt->username ? cxt->username : "(unknown)", | |
a2de6177 KZ |
996 | pam_strerror(pamh, rc)); |
997 | else | |
998 | syslog(LOG_NOTICE, | |
999 | _("FAILED LOGIN SESSION FROM %s FOR %s, %s"), | |
cea8ec53 KZ |
1000 | hostname, |
1001 | cxt->username ? cxt->username : "(unknown)", | |
a2de6177 KZ |
1002 | pam_strerror(pamh, rc)); |
1003 | ||
1004 | log_btmp(cxt); | |
1005 | log_audit(cxt, 0); | |
1006 | ||
1007 | fprintf(stderr, _("\nLogin incorrect\n")); | |
1008 | pam_end(pamh, rc); | |
f950752b | 1009 | sleepexit(EXIT_SUCCESS); |
a2de6177 KZ |
1010 | } |
1011 | } | |
1012 | ||
98306fc5 KZ |
1013 | static void loginpam_acct(struct login_context *cxt) |
1014 | { | |
1015 | int rc; | |
1016 | pam_handle_t *pamh = cxt->pamh; | |
1017 | ||
1018 | rc = pam_acct_mgmt(pamh, 0); | |
1019 | ||
1020 | if (rc == PAM_NEW_AUTHTOK_REQD) | |
1021 | rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); | |
1022 | ||
1023 | if (is_pam_failure(rc)) | |
1024 | loginpam_err(pamh, rc); | |
1025 | ||
1026 | /* | |
98306fc5 KZ |
1027 | * First get the username that we are actually using, though. |
1028 | */ | |
1029 | rc = loginpam_get_username(pamh, &cxt->username); | |
1030 | if (is_pam_failure(rc)) | |
1031 | loginpam_err(pamh, rc); | |
1032 | ||
1033 | if (!cxt->username || !*cxt->username) { | |
1034 | warnx(_("\nSession setup problem, abort.")); | |
9c6167c3 | 1035 | syslog(LOG_ERR, _("NULL user name. Abort.")); |
98306fc5 | 1036 | pam_end(pamh, PAM_SYSTEM_ERR); |
f950752b | 1037 | sleepexit(EXIT_FAILURE); |
98306fc5 KZ |
1038 | } |
1039 | } | |
1040 | ||
34f7ea15 | 1041 | /* |
80591bf6 | 1042 | * Note that the position of the pam_setcred() call is discussable: |
34f7ea15 | 1043 | * |
80591bf6 | 1044 | * - the PAM docs recommend pam_setcred() before pam_open_session() |
34f7ea15 KZ |
1045 | * - but the original RFC http://www.opengroup.org/rfc/mirror-rfc/rfc86.0.txt |
1046 | * uses pam_setcred() after pam_open_session() | |
1047 | * | |
1048 | * The old login versions (before year 2011) followed the RFC. This is probably | |
80591bf6 BS |
1049 | * not optimal, because there could be a dependence between some session modules |
1050 | * and the user's credentials. | |
34f7ea15 KZ |
1051 | * |
1052 | * The best is probably to follow openssh and call pam_setcred() before and | |
1053 | * after pam_open_session(). -- kzak@redhat.com (18-Nov-2011) | |
1054 | * | |
1055 | */ | |
59a184d9 KZ |
1056 | static void loginpam_session(struct login_context *cxt) |
1057 | { | |
1058 | int rc; | |
1059 | pam_handle_t *pamh = cxt->pamh; | |
1060 | ||
34f7ea15 | 1061 | rc = pam_setcred(pamh, PAM_ESTABLISH_CRED); |
59a184d9 KZ |
1062 | if (is_pam_failure(rc)) |
1063 | loginpam_err(pamh, rc); | |
1064 | ||
31d79197 | 1065 | rc = pam_open_session(pamh, cxt->quiet ? PAM_SILENT : 0); |
34f7ea15 KZ |
1066 | if (is_pam_failure(rc)) { |
1067 | pam_setcred(cxt->pamh, PAM_DELETE_CRED); | |
1068 | loginpam_err(pamh, rc); | |
1069 | } | |
1070 | ||
1071 | rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED); | |
59a184d9 KZ |
1072 | if (is_pam_failure(rc)) { |
1073 | pam_close_session(pamh, 0); | |
1074 | loginpam_err(pamh, rc); | |
1075 | } | |
1076 | } | |
1077 | ||
a169a454 | 1078 | /* |
80591bf6 BS |
1079 | * Detach the controlling terminal, fork, restore syslog stuff, and create |
1080 | * a new session. | |
a169a454 KZ |
1081 | */ |
1082 | static void fork_session(struct login_context *cxt) | |
1083 | { | |
1084 | struct sigaction sa, oldsa_hup, oldsa_term; | |
1085 | ||
1086 | signal(SIGALRM, SIG_DFL); | |
1087 | signal(SIGQUIT, SIG_DFL); | |
1088 | signal(SIGTSTP, SIG_IGN); | |
1089 | ||
1090 | memset(&sa, 0, sizeof(sa)); | |
1091 | sa.sa_handler = SIG_IGN; | |
1092 | sigaction(SIGINT, &sa, NULL); | |
1093 | ||
1094 | sigaction(SIGHUP, &sa, &oldsa_hup); /* ignore when TIOCNOTTY */ | |
1095 | ||
1096 | /* | |
80591bf6 BS |
1097 | * Detach the controlling tty. |
1098 | * We don't need the tty in a parent who only waits for a child. | |
1099 | * The child calls setsid() that detaches from the tty as well. | |
a169a454 KZ |
1100 | */ |
1101 | ioctl(0, TIOCNOTTY, NULL); | |
1102 | ||
1103 | /* | |
80591bf6 BS |
1104 | * We have to beware of SIGTERM, because leaving a PAM session |
1105 | * without pam_close_session() is a pretty bad thing. | |
a169a454 KZ |
1106 | */ |
1107 | sa.sa_handler = sig_handler; | |
1108 | sigaction(SIGHUP, &sa, NULL); | |
1109 | sigaction(SIGTERM, &sa, &oldsa_term); | |
1110 | ||
1111 | closelog(); | |
1112 | ||
1113 | /* | |
80591bf6 | 1114 | * We must fork before setuid(), because we need to call |
a169a454 KZ |
1115 | * pam_close_session() as root. |
1116 | */ | |
1117 | child_pid = fork(); | |
1118 | if (child_pid < 0) { | |
a169a454 KZ |
1119 | warn(_("fork failed")); |
1120 | ||
1121 | pam_setcred(cxt->pamh, PAM_DELETE_CRED); | |
1122 | pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0)); | |
f950752b | 1123 | sleepexit(EXIT_FAILURE); |
a169a454 KZ |
1124 | } |
1125 | ||
1126 | if (child_pid) { | |
1127 | /* | |
80591bf6 | 1128 | * parent - wait for child to finish, then clean up session |
a169a454 | 1129 | */ |
7679a2c5 SK |
1130 | close(STDIN_FILENO); |
1131 | close(STDOUT_FILENO); | |
1132 | close(STDERR_FILENO); | |
f4b03edb KZ |
1133 | free_getlogindefs_data(); |
1134 | ||
a169a454 KZ |
1135 | sa.sa_handler = SIG_IGN; |
1136 | sigaction(SIGQUIT, &sa, NULL); | |
1137 | sigaction(SIGINT, &sa, NULL); | |
1138 | ||
1139 | /* wait as long as any child is there */ | |
1140 | while (wait(NULL) == -1 && errno == EINTR) ; | |
1141 | openlog("login", LOG_ODELAY, LOG_AUTHPRIV); | |
1142 | ||
1143 | pam_setcred(cxt->pamh, PAM_DELETE_CRED); | |
1144 | pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0)); | |
1145 | exit(EXIT_SUCCESS); | |
1146 | } | |
1147 | ||
1148 | /* | |
1149 | * child | |
1150 | */ | |
1151 | sigaction(SIGHUP, &oldsa_hup, NULL); /* restore old state */ | |
1152 | sigaction(SIGTERM, &oldsa_term, NULL); | |
1153 | if (got_sig) | |
1154 | exit(EXIT_FAILURE); | |
1155 | ||
1156 | /* | |
455fe9a0 | 1157 | * Problem: if the user's shell is a shell like ash that doesn't do |
a169a454 KZ |
1158 | * setsid() or setpgrp(), then a ctrl-\, sending SIGQUIT to every |
1159 | * process in the pgrp, will kill us. | |
1160 | */ | |
1161 | ||
1162 | /* start new session */ | |
1163 | setsid(); | |
1164 | ||
1165 | /* make sure we have a controlling tty */ | |
1166 | open_tty(cxt->tty_path); | |
1167 | openlog("login", LOG_ODELAY, LOG_AUTHPRIV); /* reopen */ | |
1168 | ||
1169 | /* | |
1170 | * TIOCSCTTY: steal tty from other process group. | |
1171 | */ | |
1172 | if (ioctl(0, TIOCSCTTY, 1)) | |
1173 | syslog(LOG_ERR, _("TIOCSCTTY failed: %m")); | |
1174 | signal(SIGINT, SIG_DFL); | |
1175 | } | |
1176 | ||
241e4565 KZ |
1177 | /* |
1178 | * Initialize $TERM, $HOME, ... | |
1179 | */ | |
1180 | static void init_environ(struct login_context *cxt) | |
1181 | { | |
1182 | struct passwd *pwd = cxt->pwd; | |
86cf1ff4 | 1183 | char *termenv, **env; |
241e4565 KZ |
1184 | char tmp[PATH_MAX]; |
1185 | int len, i; | |
1186 | ||
1187 | termenv = getenv("TERM"); | |
86cf1ff4 KZ |
1188 | if (termenv) |
1189 | termenv = xstrdup(termenv); | |
241e4565 KZ |
1190 | |
1191 | /* destroy environment unless user has requested preservation (-p) */ | |
5941a0db SK |
1192 | if (!cxt->keep_env) |
1193 | environ = xcalloc(1, sizeof(char *)); | |
241e4565 | 1194 | |
984a6096 SK |
1195 | xsetenv("HOME", pwd->pw_dir, 0); /* legal to override */ |
1196 | xsetenv("USER", pwd->pw_name, 1); | |
1197 | xsetenv("SHELL", pwd->pw_shell, 1); | |
1198 | xsetenv("TERM", termenv ? termenv : "dumb", 1); | |
86cf1ff4 | 1199 | free(termenv); |
241e4565 | 1200 | |
05d8868d | 1201 | if (pwd->pw_uid) { |
984a6096 SK |
1202 | if (logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH) != 0) |
1203 | err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH"); | |
607e6b7c | 1204 | |
05d8868d KZ |
1205 | } else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0 && |
1206 | logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT) != 0) { | |
984a6096 | 1207 | err(EXIT_FAILURE, _("failed to set the %s environment variable"), "PATH"); |
05d8868d | 1208 | } |
9f7293ea | 1209 | |
241e4565 KZ |
1210 | /* mailx will give a funny error msg if you forget this one */ |
1211 | len = snprintf(tmp, sizeof(tmp), "%s/%s", _PATH_MAILDIR, pwd->pw_name); | |
7679a2c5 | 1212 | if (len > 0 && (size_t)len < sizeof(tmp)) |
984a6096 | 1213 | xsetenv("MAIL", tmp, 0); |
241e4565 KZ |
1214 | |
1215 | /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll | |
1216 | * not allow modifying it. | |
1217 | */ | |
984a6096 | 1218 | xsetenv("LOGNAME", pwd->pw_name, 1); |
241e4565 KZ |
1219 | |
1220 | env = pam_getenvlist(cxt->pamh); | |
1221 | for (i = 0; env && env[i]; i++) | |
1222 | putenv(env[i]); | |
1223 | } | |
1224 | ||
cbbd5185 | 1225 | /* |
80591bf6 | 1226 | * This is called for the -h option, initializes cxt->{hostname,hostaddress}. |
cbbd5185 KZ |
1227 | */ |
1228 | static void init_remote_info(struct login_context *cxt, char *remotehost) | |
1229 | { | |
92e386ca KZ |
1230 | const char *domain; |
1231 | char *p; | |
cbbd5185 KZ |
1232 | struct addrinfo hints, *info = NULL; |
1233 | ||
1234 | cxt->remote = 1; | |
1235 | ||
92e386ca | 1236 | get_thishost(cxt, &domain); |
cbbd5185 | 1237 | |
92e386ca KZ |
1238 | if (domain && (p = strchr(remotehost, '.')) && |
1239 | strcasecmp(p + 1, domain) == 0) | |
cbbd5185 KZ |
1240 | *p = '\0'; |
1241 | ||
1242 | cxt->hostname = xstrdup(remotehost); | |
1243 | ||
1244 | memset(&hints, 0, sizeof(hints)); | |
1245 | hints.ai_flags = AI_ADDRCONFIG; | |
1246 | cxt->hostaddress[0] = 0; | |
1247 | ||
1248 | if (getaddrinfo(cxt->hostname, NULL, &hints, &info) == 0 && info) { | |
1249 | if (info->ai_family == AF_INET) { | |
1250 | struct sockaddr_in *sa = | |
7679a2c5 | 1251 | (struct sockaddr_in *)info->ai_addr; |
cbbd5185 KZ |
1252 | |
1253 | memcpy(cxt->hostaddress, &(sa->sin_addr), sizeof(sa->sin_addr)); | |
1254 | ||
1255 | } else if (info->ai_family == AF_INET6) { | |
1256 | struct sockaddr_in6 *sa = | |
7679a2c5 | 1257 | (struct sockaddr_in6 *)info->ai_addr; |
1c8792f1 KZ |
1258 | #ifdef IN6_IS_ADDR_V4MAPPED |
1259 | if (IN6_IS_ADDR_V4MAPPED(&sa->sin6_addr)) { | |
1260 | const uint8_t *bytes = sa->sin6_addr.s6_addr; | |
7679a2c5 | 1261 | struct in_addr addr = { *(const in_addr_t *)(bytes + 12) }; |
cbbd5185 | 1262 | |
1c8792f1 KZ |
1263 | memcpy(cxt->hostaddress, &addr, sizeof(struct in_addr)); |
1264 | } else | |
1265 | #endif | |
1266 | memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr)); | |
cbbd5185 KZ |
1267 | } |
1268 | freeaddrinfo(info); | |
1269 | } | |
1270 | } | |
1271 | ||
7491906d RM |
1272 | static void __attribute__((__noreturn__)) usage(void) |
1273 | { | |
1274 | fputs(USAGE_HEADER, stdout); | |
1275 | printf(_(" %s [-p] [-h <host>] [-H] [[-f] <username>]\n"), program_invocation_short_name); | |
1276 | fputs(USAGE_SEPARATOR, stdout); | |
1277 | fputs(_("Begin a session on the system.\n"), stdout); | |
62cd916f RM |
1278 | |
1279 | fputs(USAGE_OPTIONS, stdout); | |
1280 | puts(_(" -p do not destroy the environment")); | |
a4376929 | 1281 | puts(_(" -f skip a login authentication")); |
62cd916f RM |
1282 | puts(_(" -h <host> hostname to be used for utmp logging")); |
1283 | puts(_(" -H suppress hostname in the login prompt")); | |
1284 | printf(" --help %s\n", USAGE_OPTSTR_HELP); | |
1285 | printf(" -V, --version %s\n", USAGE_OPTSTR_VERSION); | |
7491906d RM |
1286 | printf(USAGE_MAN_TAIL("login(1)")); |
1287 | exit(EXIT_SUCCESS); | |
1288 | } | |
1289 | ||
5dd0896a | 1290 | static void initialize(int argc, char **argv, struct login_context *cxt) |
6dbe3af9 | 1291 | { |
fc32d43e | 1292 | int c; |
0b4d75fa | 1293 | unsigned int timeout; |
1b76608e | 1294 | struct sigaction act; |
ab71156c | 1295 | |
7491906d RM |
1296 | /* the only two longopts to satisfy UL standards */ |
1297 | enum { HELP_OPTION = CHAR_MAX + 1 }; | |
5dd0896a | 1298 | const struct option longopts[] = { |
7491906d RM |
1299 | {"help", no_argument, NULL, HELP_OPTION}, |
1300 | {"version", no_argument, NULL, 'V'}, | |
1301 | {NULL, 0, NULL, 0} | |
1302 | }; | |
f17bda66 | 1303 | |
fb038d27 SK |
1304 | timeout = (unsigned int)getlogindefs_num("LOGIN_TIMEOUT", LOGIN_TIMEOUT); |
1305 | ||
f17bda66 | 1306 | /* TRANSLATORS: The standard value for %u is 60. */ |
bfcba3f5 SK |
1307 | xasprintf(&timeout_msg, _("%s: timed out after %u seconds"), |
1308 | program_invocation_short_name, timeout); | |
f17bda66 | 1309 | |
ab71156c | 1310 | signal(SIGALRM, timedout); |
5dd0896a | 1311 | sigaction(SIGALRM, NULL, &act); |
1b76608e CQ |
1312 | act.sa_flags &= ~SA_RESTART; |
1313 | sigaction(SIGALRM, &act, NULL); | |
c9baf5da | 1314 | alarm(timeout); |
ab71156c KZ |
1315 | signal(SIGQUIT, SIG_IGN); |
1316 | signal(SIGINT, SIG_IGN); | |
1317 | ||
ab71156c | 1318 | setpriority(PRIO_PROCESS, 0, 0); |
b21d741c | 1319 | process_title_init(argc, argv); |
ab71156c | 1320 | |
7491906d | 1321 | while ((c = getopt_long(argc, argv, "fHh:pV", longopts, NULL)) != -1) |
fc32d43e | 1322 | switch (c) { |
ab71156c | 1323 | case 'f': |
5dd0896a | 1324 | cxt->noauth = 1; |
ab71156c KZ |
1325 | break; |
1326 | ||
92e386ca | 1327 | case 'H': |
5dd0896a | 1328 | cxt->nohost = 1; |
92e386ca KZ |
1329 | break; |
1330 | ||
ab71156c KZ |
1331 | case 'h': |
1332 | if (getuid()) { | |
1333 | fprintf(stderr, | |
63578526 | 1334 | _("login: -h is for superuser only\n")); |
ab71156c | 1335 | exit(EXIT_FAILURE); |
ea6c190a | 1336 | } |
5dd0896a | 1337 | init_remote_info(cxt, optarg); |
ab71156c KZ |
1338 | break; |
1339 | ||
1340 | case 'p': | |
5dd0896a | 1341 | cxt->keep_env = 1; |
ab71156c KZ |
1342 | break; |
1343 | ||
0effd19e | 1344 | case 'V': |
2c308875 | 1345 | print_version(EXIT_SUCCESS); |
7491906d RM |
1346 | case HELP_OPTION: |
1347 | usage(); | |
ab71156c | 1348 | default: |
7491906d | 1349 | errtryhelp(EXIT_FAILURE); |
ea6c190a | 1350 | } |
ab71156c KZ |
1351 | argc -= optind; |
1352 | argv += optind; | |
1353 | ||
1354 | if (*argv) { | |
1355 | char *p = *argv; | |
088d4876 KZ |
1356 | |
1357 | /* username from command line */ | |
5dd0896a | 1358 | cxt->cmd_username = xstrdup(p); |
088d4876 | 1359 | /* used temporary, it'll be replaced by username from PAM or/and cxt->pwd */ |
5dd0896a | 1360 | cxt->username = cxt->cmd_username; |
ab71156c | 1361 | |
80591bf6 BS |
1362 | /* Wipe the name - some people mistype their password here. */ |
1363 | /* (Of course we are too late, but perhaps this helps a little...) */ | |
0da0a5ed SK |
1364 | #ifdef HAVE_EXPLICIT_BZERO |
1365 | explicit_bzero(p, strlen(p)); | |
1366 | #else | |
ab71156c KZ |
1367 | while (*p) |
1368 | *p++ = ' '; | |
0da0a5ed | 1369 | #endif |
ab71156c | 1370 | } |
f0649c0d | 1371 | #ifdef HAVE_CLOSE_RANGE |
cae071ed | 1372 | if (close_range(STDERR_FILENO + 1, ~0U, 0) < 0) |
f0649c0d | 1373 | #endif |
cae071ed | 1374 | ul_close_all_fds(STDERR_FILENO + 1, ~0U); |
5dd0896a SK |
1375 | } |
1376 | ||
1377 | int main(int argc, char **argv) | |
1378 | { | |
7679a2c5 SK |
1379 | char *child_argv[10]; |
1380 | int child_argc = 0; | |
5dd0896a SK |
1381 | struct passwd *pwd; |
1382 | struct login_context cxt = { | |
1383 | .tty_mode = TTY_MODE, /* tty chmod() */ | |
1384 | .pid = getpid(), /* PID */ | |
1385 | #ifdef HAVE_SECURITY_PAM_MISC_H | |
1386 | .conv = { misc_conv, NULL } /* Linux-PAM conversation function */ | |
1387 | #elif defined(HAVE_SECURITY_OPENPAM_H) | |
1388 | .conv = { openpam_ttyconv, NULL } /* OpenPAM conversation function */ | |
1389 | #endif | |
5dd0896a SK |
1390 | }; |
1391 | ||
1392 | setlocale(LC_ALL, ""); | |
1393 | bindtextdomain(PACKAGE, LOCALEDIR); | |
1394 | textdomain(PACKAGE); | |
1395 | ||
1396 | initialize(argc, argv, &cxt); | |
ab71156c | 1397 | |
99f7c131 | 1398 | setpgrp(); /* set pgid to pid this means that setsid() will fail */ |
c5bb244e | 1399 | init_tty(&cxt); |
11784a84 | 1400 | |
ab71156c | 1401 | openlog("login", LOG_ODELAY, LOG_AUTHPRIV); |
364cda48 | 1402 | |
4ab0df0a | 1403 | init_loginpam(&cxt); |
fd6b7a7f | 1404 | |
a2de6177 KZ |
1405 | /* login -f, then the user has already been authenticated */ |
1406 | cxt.noauth = cxt.noauth && getuid() == 0 ? 1 : 0; | |
ab71156c | 1407 | |
a2de6177 KZ |
1408 | if (!cxt.noauth) |
1409 | loginpam_auth(&cxt); | |
6dbe3af9 | 1410 | |
ab71156c | 1411 | /* |
98306fc5 KZ |
1412 | * Authentication may be skipped (for example, during krlogin, rlogin, |
1413 | * etc...), but it doesn't mean that we can skip other account checks. | |
80591bf6 BS |
1414 | * The account could be disabled or the password has expired (although |
1415 | * the kerberos ticket is valid). -- kzak@redhat.com (22-Feb-2006) | |
ab71156c | 1416 | */ |
98306fc5 | 1417 | loginpam_acct(&cxt); |
a7507436 | 1418 | |
4f5f35fc KZ |
1419 | cxt.pwd = xgetpwnam(cxt.username, &cxt.pwdbuf); |
1420 | if (!cxt.pwd) { | |
ab71156c | 1421 | warnx(_("\nSession setup problem, abort.")); |
9c6167c3 KZ |
1422 | syslog(LOG_ERR, _("Invalid user name \"%s\". Abort."), |
1423 | cxt.username); | |
4ab0df0a | 1424 | pam_end(cxt.pamh, PAM_SYSTEM_ERR); |
f950752b | 1425 | sleepexit(EXIT_FAILURE); |
2b6fc908 | 1426 | } |
e09947b5 | 1427 | |
67e70761 | 1428 | pwd = cxt.pwd; |
3eb8b797 | 1429 | cxt.username = pwd->pw_name; |
e09947b5 | 1430 | |
ab71156c | 1431 | /* |
a7507436 | 1432 | * Initialize the supplementary group list. This should be done before |
80591bf6 | 1433 | * pam_setcred, because PAM modules might add groups during that call. |
a7507436 | 1434 | * |
80591bf6 | 1435 | * For root we don't call initgroups, instead we call setgroups with |
a7507436 KZ |
1436 | * group 0. This avoids the need to step through the whole group file, |
1437 | * which can cause problems if NIS, NIS+, LDAP or something similar | |
1438 | * is used and the machine has network problems. | |
ab71156c | 1439 | */ |
5dd0896a SK |
1440 | { |
1441 | int retcode; | |
1442 | ||
1443 | retcode = pwd->pw_uid ? initgroups(cxt.username, pwd->pw_gid) : /* user */ | |
7679a2c5 | 1444 | setgroups(0, NULL); /* root */ |
5dd0896a SK |
1445 | if (retcode < 0) { |
1446 | syslog(LOG_ERR, _("groups initialization failed: %m")); | |
1447 | warnx(_("\nSession setup problem, abort.")); | |
1448 | pam_end(cxt.pamh, PAM_SYSTEM_ERR); | |
1449 | sleepexit(EXIT_FAILURE); | |
1450 | } | |
ab71156c KZ |
1451 | } |
1452 | ||
111b395a | 1453 | cxt.quiet = get_hushlogin_status(pwd, 1) == 1 ? 1 : 0; |
31d79197 | 1454 | |
59a184d9 | 1455 | /* |
80591bf6 | 1456 | * Open PAM session (after successful authentication and account check). |
59a184d9 KZ |
1457 | */ |
1458 | loginpam_session(&cxt); | |
e09947b5 | 1459 | |
ab71156c KZ |
1460 | /* committed to login -- turn off timeout */ |
1461 | alarm((unsigned int)0); | |
bfcba3f5 SK |
1462 | free(timeout_msg); |
1463 | timeout_msg = NULL; | |
ab71156c KZ |
1464 | |
1465 | endpwent(); | |
1466 | ||
6e3bc8a6 | 1467 | log_utmp(&cxt); |
67e70761 | 1468 | log_audit(&cxt, 1); |
3761d0bb | 1469 | log_lastlog(&cxt); |
e09947b5 | 1470 | |
ff0392a0 | 1471 | chown_tty(&cxt); |
ab71156c | 1472 | |
ab71156c KZ |
1473 | if (setgid(pwd->pw_gid) < 0 && pwd->pw_gid) { |
1474 | syslog(LOG_ALERT, _("setgid() failed")); | |
1475 | exit(EXIT_FAILURE); | |
6dbe3af9 | 1476 | } |
5c36a0eb | 1477 | |
c6f23b3b | 1478 | if (pwd->pw_shell == NULL || *pwd->pw_shell == '\0') |
ab71156c | 1479 | pwd->pw_shell = _PATH_BSHELL; |
e09947b5 | 1480 | |
516b00c4 | 1481 | init_environ(&cxt); /* init $HOME, $TERM ... */ |
ab71156c | 1482 | |
d5153819 | 1483 | process_title_update(cxt.username); |
ab71156c | 1484 | |
516b00c4 | 1485 | log_syslog(&cxt); |
ab71156c | 1486 | |
931e6098 SK |
1487 | if (!cxt.quiet) |
1488 | display_login_messages(); | |
ab71156c | 1489 | |
ab71156c | 1490 | /* |
80591bf6 BS |
1491 | * Detach the controlling terminal, fork, and create a new session |
1492 | * and reinitialize syslog stuff. | |
ab71156c | 1493 | */ |
a169a454 | 1494 | fork_session(&cxt); |
ab71156c | 1495 | |
80591bf6 | 1496 | /* discard permissions last so we can't get killed and drop core */ |
ab71156c KZ |
1497 | if (setuid(pwd->pw_uid) < 0 && pwd->pw_uid) { |
1498 | syslog(LOG_ALERT, _("setuid() failed")); | |
1499 | exit(EXIT_FAILURE); | |
1500 | } | |
1501 | ||
1502 | /* wait until here to change directory! */ | |
1503 | if (chdir(pwd->pw_dir) < 0) { | |
1504 | warn(_("%s: change directory failed"), pwd->pw_dir); | |
91d0a913 KZ |
1505 | |
1506 | if (!getlogindefs_bool("DEFAULT_HOME", 1)) | |
1507 | exit(0); | |
ab71156c KZ |
1508 | if (chdir("/")) |
1509 | exit(EXIT_FAILURE); | |
1510 | pwd->pw_dir = "/"; | |
1511 | printf(_("Logging in with home = \"/\".\n")); | |
1512 | } | |
1513 | ||
1514 | /* if the shell field has a space: treat it like a shell script */ | |
1515 | if (strchr(pwd->pw_shell, ' ')) { | |
5dd0896a SK |
1516 | char *buff; |
1517 | ||
1aaee548 | 1518 | xasprintf(&buff, "exec %s", pwd->pw_shell); |
7679a2c5 SK |
1519 | child_argv[child_argc++] = "/bin/sh"; |
1520 | child_argv[child_argc++] = "-sh"; | |
1521 | child_argv[child_argc++] = "-c"; | |
1522 | child_argv[child_argc++] = buff; | |
ab71156c | 1523 | } else { |
cbbd5185 KZ |
1524 | char tbuf[PATH_MAX + 2], *p; |
1525 | ||
ab71156c KZ |
1526 | tbuf[0] = '-'; |
1527 | xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ? | |
1528 | p + 1 : pwd->pw_shell), sizeof(tbuf) - 1); | |
1529 | ||
7679a2c5 SK |
1530 | child_argv[child_argc++] = pwd->pw_shell; |
1531 | child_argv[child_argc++] = xstrdup(tbuf); | |
ab71156c KZ |
1532 | } |
1533 | ||
7679a2c5 | 1534 | child_argv[child_argc++] = NULL; |
ab71156c | 1535 | |
7679a2c5 | 1536 | execvp(child_argv[0], child_argv + 1); |
ab71156c | 1537 | |
7679a2c5 | 1538 | if (!strcmp(child_argv[0], "/bin/sh")) |
ab71156c KZ |
1539 | warn(_("couldn't exec shell script")); |
1540 | else | |
1541 | warn(_("no shell")); | |
1542 | ||
1543 | exit(EXIT_SUCCESS); | |
6dbe3af9 | 1544 | } |