]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/login.c
login: Restore tty size after calling vhangup()
[thirdparty/util-linux.git] / login-utils / login.c
CommitLineData
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
92static char **argv0;
93static 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 */
107struct 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 145static int child_pid = 0;
42ccc9cf 146static volatile sig_atomic_t got_sig = 0;
bfcba3f5 147static 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 */
151static 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
173static 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 185static 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 */
204static 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 218static 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 224static 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 252static 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
277static 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
297static 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 317static 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);
345done:
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
359static 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 399static 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 */
436static 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
465static 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
470static 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
475static 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 */
512static 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 596static 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 630static 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
658static 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 718done:
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 */
728static 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
800static 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 828static 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 838static 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 */
854static 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
876static inline int is_pam_failure(int rc)
877{
878 return rc != PAM_SUCCESS;
879}
880
eab72c4e
KZ
881static 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
927static 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
1013static 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
1056static 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 */
1082static 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 */
1180static 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 */
1228static 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
1272static 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 1290static 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
1377int 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}