]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/login.c
tests: search for "none" by findmnt
[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
27 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
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>
fd6b7a7f 48#include <utmp.h>
6dbe3af9 49#include <stdlib.h>
6dbe3af9
KZ
50#include <sys/syslog.h>
51#include <sys/sysmacros.h>
5476bf9b 52#include <linux/major.h>
6dbe3af9 53#include <netdb.h>
cb5acd69 54#include <lastlog.h>
905045d4
KZ
55#include <security/pam_appl.h>
56#include <security/pam_misc.h>
4d8fc09c 57#include <sys/sendfile.h>
ab71156c 58
f8bdba2f
KZ
59#ifdef HAVE_LIBAUDIT
60# include <libaudit.h>
61#endif
0aeb57ac 62
cb5acd69
KZ
63#include "c.h"
64#include "setproctitle.h"
66ee8158 65#include "pathnames.h"
8abcf290 66#include "strutils.h"
7eda085c 67#include "nls.h"
3e31a2df 68#include "xalloc.h"
3761d0bb 69#include "writeall.h"
6dbe3af9 70
4d8fc09c
KZ
71#include "logindefs.h"
72
905045d4 73#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
fd6b7a7f 74
48f09788
KZ
75#define LOGIN_MAX_TRIES 3
76#define LOGIN_EXIT_TIMEOUT 5
77#define LOGIN_TIMEOUT 60
fd6b7a7f 78
fd6b7a7f 79#ifdef USE_TTY_GROUP
ab71156c 80# define TTY_MODE 0620
fd6b7a7f 81#else
ab71156c 82# define TTY_MODE 0600
fd6b7a7f
KZ
83#endif
84
ab71156c 85#define TTYGRPNAME "tty" /* name of group to own ttys */
99f7c131
KZ
86#define VCS_PATH_MAX 64
87
88/*
89 * Login control struct
90 */
91struct login_context {
92 const char *tty_path; /* ttyname() return value */
93 const char *tty_name; /* tty_path without /dev prefix */
94 const char *tty_number; /* end of the tty_path */
738246ed 95 mode_t tty_mode; /* chmod() mode */
99f7c131 96
3eb8b797
KZ
97 char *username; /* from command line or PAM */
98
67e70761
KZ
99 struct passwd *pwd; /* user info */
100
eab72c4e
KZ
101 pam_handle_t *pamh; /* PAM handler */
102 struct pam_conv conv; /* PAM conversation */
103
99f7c131
KZ
104#ifdef LOGIN_CHOWN_VCS
105 char vcsn[VCS_PATH_MAX]; /* virtual console name */
106 char vcsan[VCS_PATH_MAX];
107#endif
d20337ed 108
92e386ca
KZ
109 char thishost[MAXHOSTNAMELEN + 1]; /* this machine */
110 char *thisdomain; /* this machine domain */
111 char *hostname; /* remote machine */
112 char hostaddress[16]; /* remote address */
0180264f
KZ
113
114 pid_t pid;
3761d0bb 115 int quiet; /* 1 is hush file exists */
eab72c4e 116
a2de6177 117 unsigned int remote:1, /* login -h */
92e386ca 118 nohost:1, /* login -H */
241e4565
KZ
119 noauth:1, /* login -f */
120 keep_env:1; /* login -p */
99f7c131 121};
726f69e2 122
6dbe3af9
KZ
123/*
124 * This bounds the time given to login. Not a define so it can
125 * be patched on machines where it's too small.
126 */
a169a454
KZ
127static int timeout = LOGIN_TIMEOUT;
128static int child_pid = 0;
129static volatile int got_sig = 0;
6dbe3af9 130
9abc9dab
KZ
131/*
132 * Robert Ambrose writes:
133 * A couple of my users have a problem with login processes hanging around
134 * soaking up pts's. What they seem to hung up on is trying to write out the
135 * message 'Login timed out after %d seconds' when the connection has already
136 * been dropped.
137 * What I did was add a second timeout while trying to write the message so
138 * the process just exits if the second timeout expires.
139 */
a40f08ef
KZ
140static void __attribute__ ((__noreturn__))
141timedout2(int sig __attribute__ ((__unused__)))
9abc9dab
KZ
142{
143 struct termios ti;
144
145 /* reset echo */
146 tcgetattr(0, &ti);
147 ti.c_lflag |= ECHO;
148 tcsetattr(0, TCSANOW, &ti);
149 exit(EXIT_SUCCESS); /* %% */
150}
151
152static void timedout(int sig __attribute__ ((__unused__)))
153{
154 signal(SIGALRM, timedout2);
155 alarm(10);
156 /* TRANSLATORS: The standard value for %d is 60. */
157 warnx(_("timed out after %d seconds"), timeout);
158 signal(SIGALRM, SIG_IGN);
159 alarm(0);
160 timedout2(0);
161}
162
163/*
164 * This handler allows to inform a shell about signals to login. If you have
165 * (root) permissions you can kill all login childrent by one signal to login
166 * process.
167 *
168 * Also, parent who is session leader is able (before setsid() in child) to
169 * inform child when controlling tty goes away (e.g. modem hangup, SIGHUP).
170 */
171static void sig_handler(int signal)
172{
173 if (child_pid)
174 kill(-child_pid, signal);
175 else
176 got_sig = 1;
177 if (signal == SIGTERM)
178 kill(-child_pid, SIGHUP); /* because the shell often ignores SIGTERM */
179}
180
f950752b
KZ
181/*
182 * Let use delay for all exit() calls when user is not authenticated or
183 * session fully initialized (loginpam_session()).
184 */
a40f08ef 185static void __attribute__ ((__noreturn__)) sleepexit(int eval)
9abc9dab 186{
ca5ee2a8 187 sleep(getlogindefs_num("FAIL_DELAY", LOGIN_EXIT_TIMEOUT));
9abc9dab
KZ
188 exit(eval);
189}
190
92e386ca
KZ
191static const char *get_thishost(struct login_context *cxt, const char **domain)
192{
193 if (!*cxt->thishost) {
194 if (gethostname(cxt->thishost, sizeof(cxt->thishost))) {
195 if (domain)
196 *domain = NULL;
197 return NULL;
198 }
199 cxt->thishost[sizeof(cxt->thishost) -1] = '\0';
200 cxt->thisdomain = strchr(cxt->thishost, '.');
201 if (cxt->thisdomain)
202 *cxt->thisdomain++ = '\0';
203 }
204
205 if (domain)
206 *domain = cxt->thisdomain;
207 return cxt->thishost;
208}
209
4d8fc09c
KZ
210/*
211 * Output the /etc/motd file
212 *
213 * motd() determines the name of a login announcement file and outputs it to
214 * the user's terminal at login time. The MOTD_FILE configuration option is a
215 * colon-delimited list of filenames. The empty MOTD_FILE option disables motd
216 * printing at all.
217 */
9abc9dab
KZ
218static void motd(void)
219{
34bb8eea 220 char *motdlist, *motdfile;
4d8fc09c 221 const char *mb;
9abc9dab 222
4d8fc09c
KZ
223 mb = getlogindefs_str("MOTD_FILE", _PATH_MOTDFILE);
224 if (!mb || !*mb)
9abc9dab 225 return;
4d8fc09c
KZ
226
227 motdlist = xstrdup(mb);
228
34bb8eea
KZ
229 for (motdfile = strtok(motdlist, ":"); motdfile;
230 motdfile = strtok(NULL, ":")) {
231
4d8fc09c
KZ
232 struct stat st;
233 int fd;
234
235 if (stat(motdfile, &st) || !st.st_size)
236 continue;
237 fd = open(motdfile, O_RDONLY, 0);
238 if (fd < 0)
239 continue;
240
241 sendfile(fileno(stdout), fd, NULL, st.st_size);
242 close(fd);
243 }
34bb8eea
KZ
244
245 free(motdlist);
9abc9dab 246}
99f7c131
KZ
247
248/*
249 * Nice and simple code provided by Linus Torvalds 16-Feb-93
250 * Nonblocking stuff by Maciej W. Rozycki, macro@ds2.pg.gda.pl, 1999.
251 *
252 * He writes: "Login performs open() on a tty in a blocking mode.
253 * In some cases it may make login wait in open() for carrier infinitely,
254 * for example if the line is a simplistic case of a three-wire serial
255 * connection. I believe login should open the line in the non-blocking mode
256 * leaving the decision to make a connection to getty (where it actually
257 * belongs).
258 */
259static void open_tty(const char *tty)
ab71156c 260{
1d4ad1de
KZ
261 int i, fd, flags;
262
263 fd = open(tty, O_RDWR | O_NONBLOCK);
264 if (fd == -1) {
960cf573 265 syslog(LOG_ERR, _("FATAL: can't reopen tty: %m"));
66020e56 266 sleepexit(EXIT_FAILURE);
1d4ad1de 267 }
eb63b9b8 268
8bee984a
KZ
269 if (!isatty(fd)) {
270 close(fd);
271 syslog(LOG_ERR, _("FATAL: %s is not a terminal"), tty);
66020e56 272 sleepexit(EXIT_FAILURE);
8bee984a
KZ
273 }
274
1d4ad1de
KZ
275 flags = fcntl(fd, F_GETFL);
276 flags &= ~O_NONBLOCK;
277 fcntl(fd, F_SETFL, flags);
e09947b5 278
1d4ad1de
KZ
279 for (i = 0; i < fd; i++)
280 close(i);
281 for (i = 0; i < 3; i++)
282 if (fd != i)
283 dup2(fd, i);
284 if (fd >= 3)
285 close(fd);
6dbe3af9
KZ
286}
287
ff0392a0
KZ
288#define chown_err(_what, _uid, _gid) \
289 syslog(LOG_ERR, _("chown (%s, %lu, %lu) failed: %m"), \
290 (_what), (unsigned long) (_uid), (unsigned long) (_gid))
291
292#define chmod_err(_what, _mode) \
293 syslog(LOG_ERR, _("chmod (%s, %u) failed: %m"), (_what), (_mode))
294
295static void chown_tty(struct login_context *cxt)
296{
45d0a30e 297 const char *grname;
ff0392a0
KZ
298 uid_t uid = cxt->pwd->pw_uid;
299 gid_t gid = cxt->pwd->pw_gid;
300
45d0a30e
KZ
301 grname = getlogindefs_str("TTYGROUP", TTYGRPNAME);
302 if (grname && *grname) {
303 if (*grname >= 0 && *grname <= 9) /* group by ID */
304 gid = getlogindefs_num("TTYGROUP", gid);
305 else { /* group by name */
306 struct group *gr = getgrnam(grname);
307 if (gr)
308 gid = gr->gr_gid;
309 }
310 }
ff0392a0
KZ
311
312 if (fchown(0, uid, gid)) /* tty */
313 chown_err(cxt->tty_name, uid, gid);
738246ed
KZ
314 if (fchmod(0, cxt->tty_mode))
315 chmod_err(cxt->tty_name, cxt->tty_mode);
ff0392a0
KZ
316
317#ifdef LOGIN_CHOWN_VCS
318 if (is_consoletty(0)) {
319 if (chown(cxt->vcs, uid, gid)) /* vcs */
320 chown_err(cxt->vcs, uid, gid);
738246ed
KZ
321 if (chmod(cxt->vcs, cxt->tty_mode))
322 chmod_err(cxt->vcs, cxt->tty_mode);
ff0392a0
KZ
323
324 if (chown(cxt->vcsa, uid, gid)) /* vcsa */
325 chown_err(cxt->vcsa, uid, gid);
738246ed
KZ
326 if (chmod(cxt->vcsa, cxt->tty_mode))
327 chmod_err(cxt->vcsa, cxt->tty_mode);
ff0392a0
KZ
328 }
329#endif
330}
331
99f7c131
KZ
332/*
333 * Reads the currect terminal path and initialize cxt->tty_* variables.
334 */
335static void init_tty(struct login_context *cxt)
ab71156c 336{
99f7c131
KZ
337 const char *p;
338 struct stat st;
339 struct termios tt, ttt;
340
738246ed
KZ
341 cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE);
342
99f7c131 343 cxt->tty_path = ttyname(0); /* libc calls istty() here */
24f4bbff 344
99f7c131
KZ
345 /*
346 * In case login is suid it was possible to use a hardlink as stdin
347 * and exploit races for a local root exploit. (Wojciech Purczynski).
348 *
349 * More precisely, the problem is ttyn := ttyname(0); ...; chown(ttyn);
350 * here ttyname() might return "/tmp/x", a hardlink to a pseudotty.
351 * All of this is a problem only when login is suid, which it isnt.
352 */
353 if (!cxt->tty_path || !*cxt->tty_path ||
354 lstat(cxt->tty_path, &st) != 0 || !S_ISCHR(st.st_mode) ||
355 (st.st_nlink > 1 && strncmp(cxt->tty_path, "/dev/", 5)) ||
356 access(cxt->tty_path, R_OK | W_OK) != 0) {
66020e56 357
24f4bbff 358 syslog(LOG_ERR, _("FATAL: bad tty"));
66020e56 359 sleepexit(EXIT_FAILURE);
24f4bbff 360 }
99f7c131
KZ
361
362 if (strncmp(cxt->tty_path, "/dev/", 5) == 0)
363 cxt->tty_name = cxt->tty_path + 5;
364 else
365 cxt->tty_name = cxt->tty_path;
366
367 for (p = cxt->tty_name; p && *p; p++) {
368 if (isdigit(*p)) {
369 cxt->tty_number = p;
370 break;
371 }
372 }
373
374#ifdef LOGIN_CHOWN_VCS
375 /* find names of Virtual Console devices, for later mode change */
376 snprintf(cxt->vcsn, sizeof(cxt->vcsn), "/dev/vcs%s", cxt->tty_number);
377 snprintf(cxt->vcsan, sizeof(cxt->vcsan), "/dev/vcsa%s", cxt->tty_number);
378#endif
379
380 tcgetattr(0, &tt);
381 ttt = tt;
382 ttt.c_cflag &= ~HUPCL;
383
738246ed 384 if ((fchown(0, 0, 0) || fchmod(0, cxt->tty_mode)) && errno != EROFS) {
99f7c131
KZ
385
386 syslog(LOG_ERR, _("FATAL: %s: change permissions failed: %m"),
387 cxt->tty_path);
388 sleepexit(EXIT_FAILURE);
389 }
390
391 /* Kill processes left on this tty */
392 tcsetattr(0, TCSAFLUSH, &ttt);
393
394 signal(SIGHUP, SIG_IGN); /* so vhangup() wont kill us */
395 vhangup();
396 signal(SIGHUP, SIG_DFL);
397
398 /* open stdin,stdout,stderr to the tty */
399 open_tty(cxt->tty_path);
400
401 /* restore tty modes */
402 tcsetattr(0, TCSAFLUSH, &tt);
24f4bbff
KZ
403}
404
99f7c131 405
48d7b13a 406#ifdef LOGIN_CHOWN_VCS
fd6b7a7f 407/* true if the filedescriptor fd is a console tty, very Linux specific */
99f7c131 408static int is_consoletty(int fd)
ab71156c
KZ
409{
410 struct stat stb;
411
412 if ((fstat(fd, &stb) >= 0)
413 && (major(stb.st_rdev) == TTY_MAJOR)
414 && (minor(stb.st_rdev) < 64)) {
415 return 1;
416 }
417 return 0;
fd6b7a7f 418}
48d7b13a 419#endif
fd6b7a7f 420
364cda48
KZ
421/*
422 * Log failed login attempts in _PATH_BTMP if that exists.
423 * Must be called only with username the name of an actual user.
424 * The most common login failure is to give password instead of username.
425 */
a2de6177 426static void log_btmp(struct login_context *cxt)
ab71156c 427{
364cda48 428 struct utmp ut;
75d4dbb0 429 struct timeval tv;
364cda48
KZ
430
431 memset(&ut, 0, sizeof(ut));
432
3eb8b797
KZ
433 strncpy(ut.ut_user,
434 cxt->username ? cxt->username : "(unknown)",
364cda48
KZ
435 sizeof(ut.ut_user));
436
d20337ed
KZ
437 if (cxt->tty_number)
438 strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
439 if (cxt->tty_name)
440 xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
364cda48 441
ab71156c 442#if defined(_HAVE_UT_TV) /* in <utmpbits.h> included by <utmp.h> */
75d4dbb0
KZ
443 gettimeofday(&tv, NULL);
444 ut.ut_tv.tv_sec = tv.tv_sec;
445 ut.ut_tv.tv_usec = tv.tv_usec;
364cda48
KZ
446#else
447 {
448 time_t t;
449 time(&t);
ab71156c 450 ut.ut_time = t; /* ut_time is not always a time_t */
364cda48
KZ
451 }
452#endif
453
ab71156c 454 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
0180264f 455 ut.ut_pid = cxt->pid;
c3f974a1 456
d20337ed
KZ
457 if (cxt->hostname) {
458 xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
0468860d 459 if (*cxt->hostaddress)
d20337ed 460 memcpy(&ut.ut_addr_v6, cxt->hostaddress,
ab71156c 461 sizeof(ut.ut_addr_v6));
364cda48 462 }
6e3bc8a6 463
364cda48 464 updwtmp(_PATH_BTMP, &ut);
364cda48 465}
b2452358 466
b2452358 467
f8bdba2f 468#ifdef HAVE_LIBAUDIT
67e70761 469static void log_audit(struct login_context *cxt, int status)
f8bdba2f 470{
f8bdba2f 471 int audit_fd;
67e70761 472 struct passwd *pwd = cxt->pwd;
f8bdba2f
KZ
473
474 audit_fd = audit_open();
475 if (audit_fd == -1)
476 return;
c3f974a1
KZ
477 if (!pwd && cxt->username)
478 pwd = getpwnam(cxt->username);
479
480 audit_log_acct_message(audit_fd,
481 AUDIT_USER_LOGIN,
482 NULL,
483 "login",
484 cxt->username ? cxt->username : "(unknown)",
485 pwd ? pwd->pw_uid : (unsigned int) -1,
486 cxt->hostname,
487 NULL,
488 cxt->tty_name,
489 status);
f8bdba2f
KZ
490
491 close(audit_fd);
492}
67e70761
KZ
493#else /* !HAVE_LIBAUDIT */
494# define log_audit(cxt, status)
ab71156c 495#endif /* HAVE_LIBAUDIT */
f8bdba2f 496
3761d0bb
KZ
497static void log_lastlog(struct login_context *cxt)
498{
499 struct lastlog ll;
500 time_t t;
501 int fd;
502
67e70761
KZ
503 if (!cxt->pwd)
504 return;
505
3761d0bb
KZ
506 fd = open(_PATH_LASTLOG, O_RDWR, 0);
507 if (fd < 0)
508 return;
509
67e70761 510 lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET);
3761d0bb
KZ
511
512 /*
513 * Print last log message
514 */
515 if (!cxt->quiet) {
516 if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) &&
517 ll.ll_time != 0) {
518 time_t ll_time = (time_t) ll.ll_time;
519
520 printf(_("Last login: %.*s "), 24 - 5, ctime(&ll_time));
521 if (*ll.ll_host != '\0')
522 printf(_("from %.*s\n"),
523 (int)sizeof(ll.ll_host), ll.ll_host);
524 else
525 printf(_("on %.*s\n"),
526 (int)sizeof(ll.ll_line), ll.ll_line);
527 }
67e70761 528 lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET);
3761d0bb
KZ
529 }
530
531 memset((char *)&ll, 0, sizeof(ll));
532
533 time(&t);
534 ll.ll_time = t; /* ll_time is always 32bit */
535
536 if (cxt->tty_name)
537 xstrncpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line));
538 if (cxt->hostname)
539 xstrncpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host));
540
541 if (write_all(fd, (char *)&ll, sizeof(ll)))
542 warn(_("write lastlog failed"));
543
544 close(fd);
545}
546
6e3bc8a6
KZ
547/*
548 * Update wtmp and utmp logs
549 */
550static void log_utmp(struct login_context *cxt)
551{
552 struct utmp ut;
553 struct utmp *utp;
554 struct timeval tv;
555
556 utmpname(_PATH_UTMP);
557 setutent();
558
559 /* Find pid in utmp.
560 *
561 * login sometimes overwrites the runlevel entry in /var/run/utmp,
562 * confusing sysvinit. I added a test for the entry type, and the
563 * problem was gone. (In a runlevel entry, st_pid is not really a pid
564 * but some number calculated from the previous and current runlevel).
565 * -- Michael Riepe <michael@stud.uni-hannover.de>
566 */
567 while ((utp = getutent()))
568 if (utp->ut_pid == cxt->pid
569 && utp->ut_type >= INIT_PROCESS
570 && utp->ut_type <= DEAD_PROCESS)
571 break;
572
573 /* If we can't find a pre-existing entry by pid, try by line.
574 * BSD network daemons may rely on this.
575 */
576 if (utp == NULL) {
577 setutent();
578 ut.ut_type = LOGIN_PROCESS;
579 strncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
580 utp = getutline(&ut);
581 }
582
583 if (utp)
584 memcpy(&ut, utp, sizeof(ut));
585 else
586 /* some gettys/telnetds don't initialize utmp... */
587 memset(&ut, 0, sizeof(ut));
588
589 if (ut.ut_id[0] == 0)
590 strncpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
591
592 strncpy(ut.ut_user, cxt->username, sizeof(ut.ut_user));
593 xstrncpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
594
595#ifdef _HAVE_UT_TV /* in <utmpbits.h> included by <utmp.h> */
596 gettimeofday(&tv, NULL);
597 ut.ut_tv.tv_sec = tv.tv_sec;
598 ut.ut_tv.tv_usec = tv.tv_usec;
599#else
600 {
601 time_t t;
602 time(&t);
603 ut.ut_time = t; /* ut_time is not always a time_t */
604 /* glibc2 #defines it as ut_tv.tv_sec */
605 }
606#endif
607 ut.ut_type = USER_PROCESS;
608 ut.ut_pid = cxt->pid;
609 if (cxt->hostname) {
610 xstrncpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
0468860d 611 if (*cxt->hostaddress)
6e3bc8a6
KZ
612 memcpy(&ut.ut_addr_v6, cxt->hostaddress,
613 sizeof(ut.ut_addr_v6));
614 }
615
616 pututline(&ut);
617 endutent();
618
619 updwtmp(_PATH_WTMP, &ut);
620}
621
516b00c4
KZ
622static void log_syslog(struct login_context *cxt)
623{
624 struct passwd *pwd = cxt->pwd;
625
626 if (!strncmp(cxt->tty_name, "ttyS", 4))
627 syslog(LOG_INFO, _("DIALUP AT %s BY %s"),
628 cxt->tty_name, pwd->pw_name);
629
630 if (!pwd->pw_uid) {
631 if (cxt->hostname)
632 syslog(LOG_NOTICE, _("ROOT LOGIN ON %s FROM %s"),
633 cxt->tty_name, cxt->hostname);
634 else
635 syslog(LOG_NOTICE, _("ROOT LOGIN ON %s"), cxt->tty_name);
636 } else {
637 if (cxt->hostname)
638 syslog(LOG_INFO, _("LOGIN ON %s BY %s FROM %s"),
639 cxt->tty_name, pwd->pw_name, cxt->hostname);
640 else
641 syslog(LOG_INFO, _("LOGIN ON %s BY %s"), cxt->tty_name,
642 pwd->pw_name);
643 }
644}
645
67e70761
KZ
646static struct passwd *get_passwd_entry(const char *username,
647 char **pwdbuf,
648 struct passwd *pwd)
649{
650 struct passwd *res = NULL;
fc01644d 651 size_t sz = 16384;
67e70761
KZ
652 int x;
653
654 if (!pwdbuf || !username)
655 return NULL;
656
657#ifdef _SC_GETPW_R_SIZE_MAX
fc01644d
KZ
658 {
659 long xsz = sysconf(_SC_GETPW_R_SIZE_MAX);
660 if (xsz > 0)
661 sz = (size_t) xsz;
662 }
67e70761 663#endif
a2e84c7d 664 *pwdbuf = xrealloc(*pwdbuf, sz);
67e70761
KZ
665
666 x = getpwnam_r(username, pwd, *pwdbuf, sz, &res);
667 if (!res) {
668 errno = x;
669 return NULL;
670 }
671 return res;
672}
673
53e9eda3 674/* encapsulate stupid "void **" pam_get_item() API */
eab72c4e 675static int loginpam_get_username(pam_handle_t *pamh, char **name)
53e9eda3 676{
ab71156c 677 const void *item = (void *)*name;
53e9eda3
KZ
678 int rc;
679 rc = pam_get_item(pamh, PAM_USER, &item);
ab71156c 680 *name = (char *)item;
53e9eda3
KZ
681 return rc;
682}
53e9eda3 683
a40f08ef 684static void loginpam_err(pam_handle_t *pamh, int retcode)
905045d4
KZ
685{
686 const char *msg = pam_strerror(pamh, retcode);
687
688 if (msg) {
689 fprintf(stderr, "\n%s\n", msg);
690 syslog(LOG_ERR, "%s", msg);
691 }
692 pam_end(pamh, retcode);
f950752b 693 sleepexit(EXIT_FAILURE);
905045d4
KZ
694}
695
92e386ca
KZ
696/*
697 * Composes "<host> login: " string; or returns "login: " is -H is given
698 */
699static const char *loginpam_get_prompt(struct login_context *cxt)
700{
701 const char *host;
702 char *prompt, *dflt_prompt = _("login: ");
703 size_t sz;
704
705 if (cxt->nohost || !(host = get_thishost(cxt, NULL)))
706 return dflt_prompt;
707
708 sz = strlen(host) + 1 + strlen(dflt_prompt) + 1;
709
710 prompt = xmalloc(sz);
711 snprintf(prompt, sz, "%s %s", host, dflt_prompt);
712
713 return prompt;
714}
715
eab72c4e
KZ
716static pam_handle_t *init_loginpam(struct login_context *cxt)
717{
718 pam_handle_t *pamh = NULL;
719 int rc;
720
721 /*
722 * username is initialized to NULL and if specified on the command line
723 * it is set. Therefore, we are safe not setting it to anything
724 */
725 rc = pam_start(cxt->remote ? "remote" : "login",
726 cxt->username, &cxt->conv, &pamh);
727 if (rc != PAM_SUCCESS) {
728 warnx(_("PAM failure, aborting: %s"), pam_strerror(pamh, rc));
729 syslog(LOG_ERR, _("Couldn't initialize PAM: %s"),
730 pam_strerror(pamh, rc));
f950752b 731 sleepexit(EXIT_FAILURE);
eab72c4e
KZ
732 }
733
734 /* hostname & tty are either set to NULL or their correct values,
735 * depending on how much we know
736 */
737 rc = pam_set_item(pamh, PAM_RHOST, cxt->hostname);
738 if (is_pam_failure(rc))
739 loginpam_err(pamh, rc);
740
741 rc = pam_set_item(pamh, PAM_TTY, cxt->tty_name);
742 if (is_pam_failure(rc))
743 loginpam_err(pamh, rc);
744
745 /*
746 * Andrew.Taylor@cal.montage.ca: Provide a user prompt to PAM so that
747 * the "login: " prompt gets localized. Unfortunately, PAM doesn't have
748 * an interface to specify the "Password: " string (yet).
749 */
92e386ca 750 rc = pam_set_item(pamh, PAM_USER_PROMPT, loginpam_get_prompt(cxt));
eab72c4e
KZ
751 if (is_pam_failure(rc))
752 loginpam_err(pamh, rc);
753
754 /* we need't the original username. We have to follow PAM. */
755 free(cxt->username);
756 cxt->username = NULL;
757 cxt->pamh = pamh;
758
759 return pamh;
760}
761
a2de6177
KZ
762static void loginpam_auth(struct login_context *cxt)
763{
fab1f671 764 int rc, failcount = 0, show_unknown, retries;
cea8ec53
KZ
765 const char *hostname = cxt->hostname ? cxt->hostname :
766 cxt->tty_name ? cxt->tty_name : "<unknown>";
a2de6177
KZ
767 pam_handle_t *pamh = cxt->pamh;
768
769 /* if we didn't get a user on the command line, set it to NULL */
770 loginpam_get_username(pamh, &cxt->username);
771
cea8ec53 772 show_unknown = getlogindefs_bool("LOG_UNKFAIL_ENAB", 0);
fab1f671 773 retries = getlogindefs_num("LOGIN_RETRIES", LOGIN_MAX_TRIES);
cea8ec53 774
a2de6177
KZ
775 /*
776 * There may be better ways to deal with some of these conditions, but
777 * at least this way I don't think we'll be giving away information...
778 *
779 * Perhaps someday we can trust that all PAM modules will pay attention
fab1f671 780 * to failure count and get rid of LOGIN_MAX_TRIES?
a2de6177
KZ
781 */
782 rc = pam_authenticate(pamh, 0);
783
fab1f671 784 while ((++failcount < retries) &&
a2de6177
KZ
785 ((rc == PAM_AUTH_ERR) ||
786 (rc == PAM_USER_UNKNOWN) ||
787 (rc == PAM_CRED_INSUFFICIENT) ||
788 (rc == PAM_AUTHINFO_UNAVAIL))) {
789
cea8ec53
KZ
790 if (rc == PAM_USER_UNKNOWN && !show_unknown)
791 /*
792 * logging unknown usernames may be a security issue if
793 * an user enter her password instead of her login name
794 */
795 cxt->username = NULL;
796 else
797 loginpam_get_username(pamh, &cxt->username);
a2de6177
KZ
798
799 syslog(LOG_NOTICE,
800 _("FAILED LOGIN %d FROM %s FOR %s, %s"),
cea8ec53
KZ
801 failcount, hostname,
802 cxt->username ? cxt->username : "(unknown)",
a2de6177
KZ
803 pam_strerror(pamh, rc));
804
805 log_btmp(cxt);
806 log_audit(cxt, 0);
807
808 fprintf(stderr, _("Login incorrect\n\n"));
809
810 pam_set_item(pamh, PAM_USER, NULL);
811 rc = pam_authenticate(pamh, 0);
812 }
813
814 if (is_pam_failure(rc)) {
815
cea8ec53
KZ
816 if (rc == PAM_USER_UNKNOWN && !show_unknown)
817 cxt->username = NULL;
818 else
819 loginpam_get_username(pamh, &cxt->username);
a2de6177
KZ
820
821 if (rc == PAM_MAXTRIES)
822 syslog(LOG_NOTICE,
823 _("TOO MANY LOGIN TRIES (%d) FROM %s FOR %s, %s"),
cea8ec53
KZ
824 failcount, hostname,
825 cxt->username ? cxt->username : "(unknown)",
a2de6177
KZ
826 pam_strerror(pamh, rc));
827 else
828 syslog(LOG_NOTICE,
829 _("FAILED LOGIN SESSION FROM %s FOR %s, %s"),
cea8ec53
KZ
830 hostname,
831 cxt->username ? cxt->username : "(unknown)",
a2de6177
KZ
832 pam_strerror(pamh, rc));
833
834 log_btmp(cxt);
835 log_audit(cxt, 0);
836
837 fprintf(stderr, _("\nLogin incorrect\n"));
838 pam_end(pamh, rc);
f950752b 839 sleepexit(EXIT_SUCCESS);
a2de6177
KZ
840 }
841}
842
98306fc5
KZ
843static void loginpam_acct(struct login_context *cxt)
844{
845 int rc;
846 pam_handle_t *pamh = cxt->pamh;
847
848 rc = pam_acct_mgmt(pamh, 0);
849
850 if (rc == PAM_NEW_AUTHTOK_REQD)
851 rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
852
853 if (is_pam_failure(rc))
854 loginpam_err(pamh, rc);
855
856 /*
857 * Grab the user information out of the password file for future usage
858 * First get the username that we are actually using, though.
859 */
860 rc = loginpam_get_username(pamh, &cxt->username);
861 if (is_pam_failure(rc))
862 loginpam_err(pamh, rc);
863
864 if (!cxt->username || !*cxt->username) {
865 warnx(_("\nSession setup problem, abort."));
866 syslog(LOG_ERR, _("NULL user name in %s:%d. Abort."),
867 __FUNCTION__, __LINE__);
868 pam_end(pamh, PAM_SYSTEM_ERR);
f950752b 869 sleepexit(EXIT_FAILURE);
98306fc5
KZ
870 }
871}
872
34f7ea15
KZ
873/*
874 * Note that position of the pam_setcred() call is discussable:
875 *
876 * - the PAM docs recommends pam_setcred() before pam_open_session()
877 * - but the original RFC http://www.opengroup.org/rfc/mirror-rfc/rfc86.0.txt
878 * uses pam_setcred() after pam_open_session()
879 *
880 * The old login versions (before year 2011) followed the RFC. This is probably
881 * not optimal, because there could be dependence between some session modules
882 * and user's credentials.
883 *
884 * The best is probably to follow openssh and call pam_setcred() before and
885 * after pam_open_session(). -- kzak@redhat.com (18-Nov-2011)
886 *
887 */
59a184d9
KZ
888static void loginpam_session(struct login_context *cxt)
889{
890 int rc;
891 pam_handle_t *pamh = cxt->pamh;
892
34f7ea15 893 rc = pam_setcred(pamh, PAM_ESTABLISH_CRED);
59a184d9
KZ
894 if (is_pam_failure(rc))
895 loginpam_err(pamh, rc);
896
34f7ea15
KZ
897 rc = pam_open_session(pamh, 0);
898 if (is_pam_failure(rc)) {
899 pam_setcred(cxt->pamh, PAM_DELETE_CRED);
900 loginpam_err(pamh, rc);
901 }
902
903 rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED);
59a184d9
KZ
904 if (is_pam_failure(rc)) {
905 pam_close_session(pamh, 0);
906 loginpam_err(pamh, rc);
907 }
908}
909
3c0e680c
KZ
910/*
911 * We need to check effective UID/GID. For example $HOME could be on root
912 * squashed NFS or on NFS with UID mapping and access(2) uses real UID/GID.
913 * The open(2) seems as the surest solution.
914 * -- kzak@redhat.com (10-Apr-2009)
915 */
918b1a9d 916static int effective_access(const char *path, int mode)
3c0e680c
KZ
917{
918 int fd = open(path, mode);
919 if (fd != -1)
920 close(fd);
921 return fd == -1 ? -1 : 0;
922}
923
918b1a9d
KZ
924/*
925 * Check per accout or global hush-login setting.
926 *
927 * Hushed mode is enabled:
928 *
929 * a) if global (e.g. /etc/hushlogins) hush file exists:
930 * 1) for ALL ACCOUNTS if the file is empty
931 * 2) for the current user if the username or shell are found in the file
932 *
933 * b) if ~/.hushlogin file exists
934 *
935 * The ~/.hushlogin is ignored if the global hush file exists.
936 *
84d3c9ff
KZ
937 * The HUSHLOGIN_FILE login.def variable overwrites the default hush filename.
938 *
918b1a9d
KZ
939 * Note that shadow-utils login(1) does not support "a1)". The "a1)" is
940 * necessary if you want to use PAM for "Last login" message.
941 *
942 * -- Karel Zak <kzak@redhat.com> (26-Aug-2011)
943 *
944 *
945 * Per-account check requires some explanation: As root we may not be able to
946 * read the directory of the user if it is on an NFS mounted filesystem. We
947 * temporarily set our effective uid to the user-uid making sure that we keep
948 * root privs. in the real uid.
949 *
950 * A portable solution would require a fork(), but we rely on Linux having the
951 * BSD setreuid()
952 */
953static int get_hushlogin_status(struct passwd *pwd)
954{
955 const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL };
84d3c9ff 956 const char *file;
918b1a9d
KZ
957 char buf[BUFSIZ];
958 int i;
959
84d3c9ff
KZ
960 file = getlogindefs_str("HUSHLOGIN_FILE", NULL);
961 if (file) {
962 if (!*file)
963 return 0; /* empty HUSHLOGIN_FILE defined */
964
965 files[0] = file;
966 files[1] = NULL;
967 }
968
918b1a9d 969 for (i = 0; files[i]; i++) {
918b1a9d
KZ
970 int ok = 0;
971
84d3c9ff
KZ
972 file = files[i];
973
918b1a9d
KZ
974 /* Global hush-file*/
975 if (*file == '/') {
976 struct stat st;
977 FILE *f;
978
979 if (stat(file, &st) != 0)
980 continue; /* file does not exist */
981
982 if (st.st_size == 0)
983 return 1; /* for all accounts */
984
985 f = fopen(file, "r");
986 if (!f)
987 continue; /* ignore errors... */
988
989 while (ok == 0 && fgets(buf, sizeof(buf), f)) {
990 buf[strlen(buf) - 1] = '\0';
991 ok = !strcmp(buf, *buf == '/' ? pwd->pw_shell :
992 pwd->pw_name);
993 }
994 fclose(f);
995 if (ok)
996 return 1; /* found username/shell */
997
998 return 0; /* ignore per-account files */
999 }
1000
1001 /* Per-account setting */
1002 if (strlen(pwd->pw_dir) + sizeof(file) + 2 > sizeof(buf))
1003 continue;
1004 else {
1005 uid_t ruid = getuid();
1006 gid_t egid = getegid();
1007
1008 sprintf(buf, "%s/%s", pwd->pw_dir, file);
1009 setregid(-1, pwd->pw_gid);
1010 setreuid(0, pwd->pw_uid);
1011 ok = effective_access(buf, O_RDONLY) == 0;
1012 setuid(0); /* setreuid doesn't do it alone! */
1013 setreuid(ruid, 0);
1014 setregid(-1, egid);
1015
1016 if (ok)
1017 return 1; /* enabled by user */
1018 }
1019 }
1020
1021 return 0;
1022}
1023
a169a454
KZ
1024/*
1025 * Detach the controlling terminal, fork, restore syslog stuff and create a new
1026 * session.
1027 */
1028static void fork_session(struct login_context *cxt)
1029{
1030 struct sigaction sa, oldsa_hup, oldsa_term;
1031
1032 signal(SIGALRM, SIG_DFL);
1033 signal(SIGQUIT, SIG_DFL);
1034 signal(SIGTSTP, SIG_IGN);
1035
1036 memset(&sa, 0, sizeof(sa));
1037 sa.sa_handler = SIG_IGN;
1038 sigaction(SIGINT, &sa, NULL);
1039
1040 sigaction(SIGHUP, &sa, &oldsa_hup); /* ignore when TIOCNOTTY */
1041
1042 /*
1043 * detach the controlling tty
1044 * -- we needn't the tty in parent who waits for child only.
1045 * The child calls setsid() that detach from the tty as well.
1046 */
1047 ioctl(0, TIOCNOTTY, NULL);
1048
1049 /*
1050 * We have care about SIGTERM, because leave PAM session without
1051 * pam_close_session() is pretty bad thing.
1052 */
1053 sa.sa_handler = sig_handler;
1054 sigaction(SIGHUP, &sa, NULL);
1055 sigaction(SIGTERM, &sa, &oldsa_term);
1056
1057 closelog();
1058
1059 /*
1060 * We must fork before setuid() because we need to call
1061 * pam_close_session() as root.
1062 */
1063 child_pid = fork();
1064 if (child_pid < 0) {
1065 /*
1066 * fork() error
1067 */
1068 warn(_("fork failed"));
1069
1070 pam_setcred(cxt->pamh, PAM_DELETE_CRED);
1071 pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
f950752b 1072 sleepexit(EXIT_FAILURE);
a169a454
KZ
1073 }
1074
1075 if (child_pid) {
1076 /*
1077 * parent - wait for child to finish, then cleanup session
1078 */
1079 close(0);
1080 close(1);
1081 close(2);
1082 sa.sa_handler = SIG_IGN;
1083 sigaction(SIGQUIT, &sa, NULL);
1084 sigaction(SIGINT, &sa, NULL);
1085
1086 /* wait as long as any child is there */
1087 while (wait(NULL) == -1 && errno == EINTR) ;
1088 openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
1089
1090 pam_setcred(cxt->pamh, PAM_DELETE_CRED);
1091 pam_end(cxt->pamh, pam_close_session(cxt->pamh, 0));
1092 exit(EXIT_SUCCESS);
1093 }
1094
1095 /*
1096 * child
1097 */
1098 sigaction(SIGHUP, &oldsa_hup, NULL); /* restore old state */
1099 sigaction(SIGTERM, &oldsa_term, NULL);
1100 if (got_sig)
1101 exit(EXIT_FAILURE);
1102
1103 /*
1104 * Problem: if the user's shell is a shell like ash that doesnt do
1105 * setsid() or setpgrp(), then a ctrl-\, sending SIGQUIT to every
1106 * process in the pgrp, will kill us.
1107 */
1108
1109 /* start new session */
1110 setsid();
1111
1112 /* make sure we have a controlling tty */
1113 open_tty(cxt->tty_path);
1114 openlog("login", LOG_ODELAY, LOG_AUTHPRIV); /* reopen */
1115
1116 /*
1117 * TIOCSCTTY: steal tty from other process group.
1118 */
1119 if (ioctl(0, TIOCSCTTY, 1))
1120 syslog(LOG_ERR, _("TIOCSCTTY failed: %m"));
1121 signal(SIGINT, SIG_DFL);
1122}
1123
241e4565
KZ
1124/*
1125 * Initialize $TERM, $HOME, ...
1126 */
1127static void init_environ(struct login_context *cxt)
1128{
1129 struct passwd *pwd = cxt->pwd;
1130 char *termenv = NULL, **env;
1131 char tmp[PATH_MAX];
1132 int len, i;
1133
1134 termenv = getenv("TERM");
a2e84c7d 1135 termenv = termenv ? xstrdup(termenv) : "dumb";
241e4565
KZ
1136
1137 /* destroy environment unless user has requested preservation (-p) */
1138 if (!cxt->keep_env) {
a2e84c7d 1139 environ = (char **) xmalloc(sizeof(char *));
241e4565
KZ
1140 memset(environ, 0, sizeof(char *));
1141 }
1142
1143 setenv("HOME", pwd->pw_dir, 0); /* legal to override */
241e4565
KZ
1144 setenv("SHELL", pwd->pw_shell, 1);
1145 setenv("TERM", termenv, 1);
1146
9f7293ea 1147 if (pwd->pw_uid)
607e6b7c
KZ
1148 logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
1149
1150 else if (logindefs_setenv("PATH", "ENV_ROOTPATH", NULL) != 0)
1151 logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
9f7293ea 1152
241e4565
KZ
1153 /* mailx will give a funny error msg if you forget this one */
1154 len = snprintf(tmp, sizeof(tmp), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
1155 if (len > 0 && (size_t) len + 1 <= sizeof(tmp))
1156 setenv("MAIL", tmp, 0);
1157
1158 /* LOGNAME is not documented in login(1) but HP-UX 6.5 does it. We'll
1159 * not allow modifying it.
1160 */
1161 setenv("LOGNAME", pwd->pw_name, 1);
1162
1163 env = pam_getenvlist(cxt->pamh);
1164 for (i = 0; env && env[i]; i++)
1165 putenv(env[i]);
1166}
1167
cbbd5185
KZ
1168/*
1169 * Called for -h option, initialize cxt->{hostname,hostaddress}
1170 */
1171static void init_remote_info(struct login_context *cxt, char *remotehost)
1172{
92e386ca
KZ
1173 const char *domain;
1174 char *p;
cbbd5185
KZ
1175 struct addrinfo hints, *info = NULL;
1176
1177 cxt->remote = 1;
1178
92e386ca 1179 get_thishost(cxt, &domain);
cbbd5185 1180
92e386ca
KZ
1181 if (domain && (p = strchr(remotehost, '.')) &&
1182 strcasecmp(p + 1, domain) == 0)
cbbd5185
KZ
1183 *p = '\0';
1184
1185 cxt->hostname = xstrdup(remotehost);
1186
1187 memset(&hints, 0, sizeof(hints));
1188 hints.ai_flags = AI_ADDRCONFIG;
1189 cxt->hostaddress[0] = 0;
1190
1191 if (getaddrinfo(cxt->hostname, NULL, &hints, &info) == 0 && info) {
1192 if (info->ai_family == AF_INET) {
1193 struct sockaddr_in *sa =
1194 (struct sockaddr_in *) info->ai_addr;
1195
1196 memcpy(cxt->hostaddress, &(sa->sin_addr), sizeof(sa->sin_addr));
1197
1198 } else if (info->ai_family == AF_INET6) {
1199 struct sockaddr_in6 *sa =
1200 (struct sockaddr_in6 *) info->ai_addr;
1201
1202 memcpy(cxt->hostaddress, &(sa->sin6_addr), sizeof(sa->sin6_addr));
1203 }
1204 freeaddrinfo(info);
1205 }
1206}
1207
ab71156c 1208int main(int argc, char **argv)
6dbe3af9 1209{
fc32d43e 1210 int c;
241e4565 1211 int cnt;
ab71156c
KZ
1212 char *childArgv[10];
1213 char *buff;
1214 int childArgc = 0;
1215 int retcode;
6dbe3af9 1216
67e70761
KZ
1217 char *pwdbuf = NULL;
1218 struct passwd *pwd = NULL, _pwd;
1219
0180264f 1220 struct login_context cxt = {
738246ed 1221 .tty_mode = TTY_MODE, /* tty chmod() */
eab72c4e
KZ
1222 .pid = getpid(), /* PID */
1223 .conv = { misc_conv, NULL } /* PAM conversation function */
0180264f 1224 };
ab71156c 1225
9abd9cde
KZ
1226 timeout = getlogindefs_num("LOGIN_TIMEOUT", LOGIN_TIMEOUT);
1227
ab71156c
KZ
1228 signal(SIGALRM, timedout);
1229 siginterrupt(SIGALRM, 1); /* we have to interrupt syscalls like ioclt() */
1230 alarm((unsigned int)timeout);
1231 signal(SIGQUIT, SIG_IGN);
1232 signal(SIGINT, SIG_IGN);
1233
1234 setlocale(LC_ALL, "");
1235 bindtextdomain(PACKAGE, LOCALEDIR);
1236 textdomain(PACKAGE);
1237
1238 setpriority(PRIO_PROCESS, 0, 0);
1239 initproctitle(argc, argv);
1240
1241 /*
1242 * -p is used by getty to tell login not to destroy the environment
1243 * -f is used to skip a second login authentication
1244 * -h is used by other servers to pass the name of the remote
1245 * host to login so that it may be placed in utmp and wtmp
1246 */
0effd19e 1247 while ((c = getopt(argc, argv, "fHh:pV")) != -1)
fc32d43e 1248 switch (c) {
ab71156c 1249 case 'f':
a2de6177 1250 cxt.noauth = 1;
ab71156c
KZ
1251 break;
1252
92e386ca
KZ
1253 case 'H':
1254 cxt.nohost = 1;
1255 break;
1256
ab71156c
KZ
1257 case 'h':
1258 if (getuid()) {
1259 fprintf(stderr,
1260 _("login: -h for super-user only.\n"));
1261 exit(EXIT_FAILURE);
ea6c190a 1262 }
cbbd5185 1263 init_remote_info(&cxt, optarg);
ab71156c
KZ
1264 break;
1265
1266 case 'p':
241e4565 1267 cxt.keep_env = 1;
ab71156c
KZ
1268 break;
1269
0effd19e
SK
1270 case 'V':
1271 printf(UTIL_LINUX_VERSION);
1272 return EXIT_SUCCESS;
ab71156c
KZ
1273 case '?':
1274 default:
92e386ca 1275 fprintf(stderr, _("usage: login [ -p ] [ -h host ] [ -H ] [ -f username | username ]\n"));
ab71156c 1276 exit(EXIT_FAILURE);
ea6c190a 1277 }
ab71156c
KZ
1278 argc -= optind;
1279 argv += optind;
1280
1281 if (*argv) {
1282 char *p = *argv;
a2e84c7d 1283 cxt.username = xstrdup(p);
ab71156c
KZ
1284
1285 /* wipe name - some people mistype their password here */
1286 /* (of course we are too late, but perhaps this helps a little ..) */
1287 while (*p)
1288 *p++ = ' ';
1289 }
1290
1291 for (cnt = getdtablesize(); cnt > 2; cnt--)
1292 close(cnt);
1293
99f7c131 1294 setpgrp(); /* set pgid to pid this means that setsid() will fail */
11784a84 1295
ab71156c 1296 openlog("login", LOG_ODELAY, LOG_AUTHPRIV);
364cda48 1297
99f7c131 1298 init_tty(&cxt);
4ab0df0a 1299 init_loginpam(&cxt);
fd6b7a7f 1300
a2de6177
KZ
1301 /* login -f, then the user has already been authenticated */
1302 cxt.noauth = cxt.noauth && getuid() == 0 ? 1 : 0;
ab71156c 1303
a2de6177
KZ
1304 if (!cxt.noauth)
1305 loginpam_auth(&cxt);
6dbe3af9 1306
ab71156c 1307 /*
98306fc5
KZ
1308 * Authentication may be skipped (for example, during krlogin, rlogin,
1309 * etc...), but it doesn't mean that we can skip other account checks.
1310 * The account could be disabled or password expired (althought
1311 * kerberos ticket is valid). -- kzak@redhat.com (22-Feb-2006)
ab71156c 1312 */
98306fc5 1313 loginpam_acct(&cxt);
a7507436 1314
67e70761 1315 if (!(cxt.pwd = get_passwd_entry(cxt.username, &pwdbuf, &_pwd))) {
ab71156c
KZ
1316 warnx(_("\nSession setup problem, abort."));
1317 syslog(LOG_ERR, _("Invalid user name \"%s\" in %s:%d. Abort."),
3eb8b797 1318 cxt.username, __FUNCTION__, __LINE__);
4ab0df0a 1319 pam_end(cxt.pamh, PAM_SYSTEM_ERR);
f950752b 1320 sleepexit(EXIT_FAILURE);
2b6fc908 1321 }
e09947b5 1322
67e70761 1323 pwd = cxt.pwd;
3eb8b797 1324 cxt.username = pwd->pw_name;
e09947b5 1325
ab71156c 1326 /*
a7507436
KZ
1327 * Initialize the supplementary group list. This should be done before
1328 * pam_setcred because the PAM modules might add groups during
1329 * pam_setcred.
1330 *
1331 * For root we don't call initgroups, instead we call setgroups with
1332 * group 0. This avoids the need to step through the whole group file,
1333 * which can cause problems if NIS, NIS+, LDAP or something similar
1334 * is used and the machine has network problems.
ab71156c 1335 */
a7507436
KZ
1336 retcode = pwd->pw_uid ? initgroups(cxt.username, pwd->pw_gid) : /* user */
1337 setgroups(0, NULL); /* root */
1338 if (retcode < 0) {
1339 syslog(LOG_ERR, _("groups initialization failed: %m"));
ab71156c 1340 warnx(_("\nSession setup problem, abort."));
4ab0df0a 1341 pam_end(cxt.pamh, PAM_SYSTEM_ERR);
f950752b 1342 sleepexit(EXIT_FAILURE);
ab71156c
KZ
1343 }
1344
59a184d9
KZ
1345 /*
1346 * Open PAM session (after successful authentication and account check)
1347 */
1348 loginpam_session(&cxt);
e09947b5 1349
ab71156c
KZ
1350 /* committed to login -- turn off timeout */
1351 alarm((unsigned int)0);
1352
1353 endpwent();
1354
918b1a9d 1355 cxt.quiet = get_hushlogin_status(pwd);
e09947b5 1356
6e3bc8a6 1357 log_utmp(&cxt);
67e70761 1358 log_audit(&cxt, 1);
3761d0bb 1359 log_lastlog(&cxt);
e09947b5 1360
ff0392a0 1361 chown_tty(&cxt);
ab71156c 1362
ab71156c
KZ
1363 if (setgid(pwd->pw_gid) < 0 && pwd->pw_gid) {
1364 syslog(LOG_ALERT, _("setgid() failed"));
1365 exit(EXIT_FAILURE);
6dbe3af9 1366 }
5c36a0eb 1367
c6f23b3b 1368 if (pwd->pw_shell == NULL || *pwd->pw_shell == '\0')
ab71156c 1369 pwd->pw_shell = _PATH_BSHELL;
e09947b5 1370
516b00c4 1371 init_environ(&cxt); /* init $HOME, $TERM ... */
ab71156c 1372
3eb8b797 1373 setproctitle("login", cxt.username);
ab71156c 1374
516b00c4 1375 log_syslog(&cxt);
ab71156c 1376
3761d0bb 1377 if (!cxt.quiet) {
ab71156c
KZ
1378 motd();
1379
1380#ifdef LOGIN_STAT_MAIL
1381 /*
1382 * This turns out to be a bad idea: when the mail spool
1383 * is NFS mounted, and the NFS connection hangs, the
1384 * login hangs, even root cannot login.
1385 * Checking for mail should be done from the shell.
1386 */
1387 {
1388 struct stat st;
1389 char *mail;
1390
1391 mail = getenv("MAIL");
1392 if (mail && stat(mail, &st) == 0 && st.st_size != 0) {
1393 if (st.st_mtime > st.st_atime)
1394 printf(_("You have new mail.\n"));
1395 else
1396 printf(_("You have mail.\n"));
1397 }
1398 }
d162fcb5 1399#endif
ab71156c
KZ
1400 }
1401
ab71156c 1402 /*
a169a454
KZ
1403 * Detach the controlling terminal, fork() and create, new session
1404 * and reinilizalize syslog stuff.
ab71156c 1405 */
a169a454 1406 fork_session(&cxt);
ab71156c
KZ
1407
1408 /* discard permissions last so can't get killed and drop core */
1409 if (setuid(pwd->pw_uid) < 0 && pwd->pw_uid) {
1410 syslog(LOG_ALERT, _("setuid() failed"));
1411 exit(EXIT_FAILURE);
1412 }
1413
1414 /* wait until here to change directory! */
1415 if (chdir(pwd->pw_dir) < 0) {
1416 warn(_("%s: change directory failed"), pwd->pw_dir);
91d0a913
KZ
1417
1418 if (!getlogindefs_bool("DEFAULT_HOME", 1))
1419 exit(0);
ab71156c
KZ
1420 if (chdir("/"))
1421 exit(EXIT_FAILURE);
1422 pwd->pw_dir = "/";
1423 printf(_("Logging in with home = \"/\".\n"));
1424 }
1425
1426 /* if the shell field has a space: treat it like a shell script */
1427 if (strchr(pwd->pw_shell, ' ')) {
1428 buff = xmalloc(strlen(pwd->pw_shell) + 6);
1429
1430 strcpy(buff, "exec ");
1431 strcat(buff, pwd->pw_shell);
1432 childArgv[childArgc++] = "/bin/sh";
1433 childArgv[childArgc++] = "-sh";
1434 childArgv[childArgc++] = "-c";
1435 childArgv[childArgc++] = buff;
1436 } else {
cbbd5185
KZ
1437 char tbuf[PATH_MAX + 2], *p;
1438
ab71156c
KZ
1439 tbuf[0] = '-';
1440 xstrncpy(tbuf + 1, ((p = strrchr(pwd->pw_shell, '/')) ?
1441 p + 1 : pwd->pw_shell), sizeof(tbuf) - 1);
1442
1443 childArgv[childArgc++] = pwd->pw_shell;
2f228f8c 1444 childArgv[childArgc++] = xstrdup(tbuf);
ab71156c
KZ
1445 }
1446
1447 childArgv[childArgc++] = NULL;
1448
1449 execvp(childArgv[0], childArgv + 1);
1450
1451 if (!strcmp(childArgv[0], "/bin/sh"))
1452 warn(_("couldn't exec shell script"));
1453 else
1454 warn(_("no shell"));
1455
1456 exit(EXIT_SUCCESS);
6dbe3af9
KZ
1457}
1458
df1dddf9 1459