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