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