You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
util-linux/login-utils/login.c

1389 lines
36 KiB

/*
* login(1)
*
* This program is derived from 4.3 BSD software and is subject to the
* copyright notice below.
*
* Copyright (C) 2011 Karel Zak <kzak@redhat.com>
* Rewritten to PAM-only version.
*
* Michael Glad (glad@daimi.dk)
* Computer Science Department, Aarhus University, Denmark
* 1990-07-04
*
* Copyright (c) 1980, 1987, 1988 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <sys/param.h>
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <getopt.h>
#include <memory.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/file.h>
#include <termios.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <utmpx.h>
#ifdef HAVE_LASTLOG_H
# include <lastlog.h>
#endif
#include <stdlib.h>
#include <sys/syslog.h>
#ifdef HAVE_LINUX_MAJOR_H
# include <linux/major.h>
#endif
#include <netdb.h>
#include <security/pam_appl.h>
#ifdef HAVE_SECURITY_PAM_MISC_H
# include <security/pam_misc.h>
#elif defined(HAVE_SECURITY_OPENPAM_H)
# include <security/openpam.h>
#endif
#include <sys/sendfile.h>
#ifdef HAVE_LIBAUDIT
# include <libaudit.h>
#endif
#include "c.h"
#include "setproctitle.h"
#include "pathnames.h"
#include "strutils.h"
#include "nls.h"
#include "env.h"
#include "xalloc.h"
#include "all-io.h"
#include "fileutils.h"
#include "timeutils.h"
#include "ttyutils.h"
#include "pwdutils.h"
#include "logindefs.h"
#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
#define LOGIN_MAX_TRIES 3
#define LOGIN_EXIT_TIMEOUT 5
#define LOGIN_TIMEOUT 60
#ifdef USE_TTY_GROUP
# define TTY_MODE 0620
#else
# define TTY_MODE 0600
#endif
#define TTYGRPNAME "tty" /* name of group to own ttys */
#define VCS_PATH_MAX 64
/*
* Login control struct
*/
struct login_context {
const char *tty_path; /* ttyname() return value */
const char *tty_name; /* tty_path without /dev prefix */
const char *tty_number; /* end of the tty_path */
mode_t tty_mode; /* chmod() mode */
const char *username; /* points to PAM, pwd or cmd_username */
char *cmd_username; /* username specified on command line */
struct passwd *pwd; /* user info */
char *pwdbuf; /* pwd strings */
pam_handle_t *pamh; /* PAM handler */
struct pam_conv conv; /* PAM conversation */
#ifdef LOGIN_CHOWN_VCS
char vcsn[VCS_PATH_MAX]; /* virtual console name */
char vcsan[VCS_PATH_MAX];
#endif
char *thishost; /* this machine */
char *thisdomain; /* this machine's domain */
char *hostname; /* remote machine */
char hostaddress[16]; /* remote address */
pid_t pid;
int quiet; /* 1 if hush file exists */
unsigned int remote:1, /* login -h */
nohost:1, /* login -H */
noauth:1, /* login -f */
keep_env:1; /* login -p */
};
/*
* This bounds the time given to login. Not a define, so it can
* be patched on machines where it's too small.
*/
static unsigned int timeout = LOGIN_TIMEOUT;
static int child_pid = 0;
static volatile int got_sig = 0;
static char timeout_msg[128];
#ifdef LOGIN_CHOWN_VCS
/* true if the filedescriptor fd is a console tty, very Linux specific */
static int is_consoletty(int fd)
{
struct stat stb;
if ((fstat(fd, &stb) >= 0)
&& (major(stb.st_rdev) == TTY_MAJOR)
&& (minor(stb.st_rdev) < 64)) {
return 1;
}
return 0;
}
#endif
/*
* Robert Ambrose writes:
* A couple of my users have a problem with login processes hanging around
* soaking up pts's. What they seem to hung up on is trying to write out the
* message 'Login timed out after %d seconds' when the connection has already
* been dropped.
* What I did was add a second timeout while trying to write the message, so
* the process just exits if the second timeout expires.
*/
static void __attribute__ ((__noreturn__))
timedout2(int sig __attribute__ ((__unused__)))
{
struct termios ti;
/* reset echo */
tcgetattr(0, &ti);
ti.c_lflag |= ECHO;
tcsetattr(0, TCSANOW, &ti);
_exit(EXIT_SUCCESS); /* %% */
}
static void timedout(int sig __attribute__ ((__unused__)))
{
signal(SIGALRM, timedout2);
alarm(10);
ignore_result( write(STDERR_FILENO, timeout_msg, strlen(timeout_msg)) );
signal(SIGALRM, SIG_IGN);
alarm(0);
timedout2(0);
}
/*
* This handler allows to inform a shell about signals to login. If you have
* (root) permissions, you can kill all login children by one signal to the
* login process.
*
* Also, a parent who is session leader is able (before setsid() in the child)
* to inform the child when the controlling tty goes away (e.g. modem hangup).
*/
static void sig_handler(int signal)
{
if (child_pid)
kill(-child_pid, signal);
else
got_sig = 1;
if (signal == SIGTERM)
kill(-child_pid, SIGHUP); /* because the shell often ignores SIGTERM */
}
/*
* Let us delay all exit() calls when the user is not authenticated
* or the session not fully initialized (loginpam_session()).
*/
static void __attribute__ ((__noreturn__)) sleepexit(int eval)
{
sleep((unsigned int)getlogindefs_num("FAIL_DELAY", LOGIN_EXIT_TIMEOUT));
exit(eval);
}
static const char *get_thishost(struct login_context *cxt, const char **domain)
{
if (!cxt->thishost) {
cxt->thishost = xgethostname();
if (!cxt->thishost) {
if (domain)
*domain = NULL;
return NULL;
}
cxt->thisdomain = strchr(cxt->thishost, '.');
if (cxt->thisdomain)
*cxt->thisdomain++ = '\0';
}
if (domain)
*domain = cxt->thisdomain;
return cxt->thishost;
}
/*
* Output the /etc/motd file.
*
* It determines the name of a login announcement file and outputs it to the
* user's terminal at login time. The MOTD_FILE configuration option is a
* colon-delimited list of filenames. An empty MOTD_FILE option disables
* message-of-the-day printing completely.
*/
static void motd(void)
{
char *motdlist, *motdfile;
const char *mb;
mb = getlogindefs_str("MOTD_FILE", _PATH_MOTDFILE);
if (!mb || !*mb)
return;
motdlist = xstrdup(mb);
for (motdfile = strtok(motdlist, ":"); motdfile;
motdfile = strtok(NULL, ":")) {
struct stat st;
int fd;
fd = open(motdfile, O_RDONLY, 0);
if (fd < 0)
continue;
if (!fstat(fd, &st) && st.st_size)
sendfile(fileno(stdout), fd, NULL, st.st_size);
close(fd);
}
free(motdlist);
}
/*
* Nice and simple code provided by Linus Torvalds 16-Feb-93.
* Non-blocking stuff by Maciej W. Rozycki, macro@ds2.pg.gda.pl, 1999.
*
* He writes: "Login performs open() on a tty in a blocking mode.
* In some cases it may make login wait in open() for carrier infinitely,
* for example if the line is a simplistic case of a three-wire serial
* connection. I believe login should open the line in non-blocking mode,
* leaving the decision to make a connection to getty (where it actually
* belongs)."
*/
static void open_tty(const char *tty)
{
int i, fd, flags;
fd = open(tty, O_RDWR | O_NONBLOCK);
if (fd == -1) {
syslog(LOG_ERR, _("FATAL: can't reopen tty: %m"));
sleepexit(EXIT_FAILURE);
}
if (!isatty(fd)) {
close(fd);
syslog(LOG_ERR, _("FATAL: %s is not a terminal"), tty);
sleepexit(EXIT_FAILURE);
}
flags = fcntl(fd, F_GETFL);
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
for (i = 0; i < fd; i++)
close(i);
for (i = 0; i < 3; i++)
if (fd != i)
dup2(fd, i);
if (fd >= 3)
close(fd);
}
#define chown_err(_what, _uid, _gid) \
syslog(LOG_ERR, _("chown (%s, %lu, %lu) failed: %m"), \
(_what), (unsigned long) (_uid), (unsigned long) (_gid))
#define chmod_err(_what, _mode) \
syslog(LOG_ERR, _("chmod (%s, %u) failed: %m"), (_what), (_mode))
static void chown_tty(struct login_context *cxt)
{
const char *grname;
uid_t uid = cxt->pwd->pw_uid;
gid_t gid = cxt->pwd->pw_gid;
grname = getlogindefs_str("TTYGROUP", TTYGRPNAME);
if (grname && *grname) {
struct group *gr = getgrnam(grname);
if (gr) /* group by name */
gid = gr->gr_gid;
else /* group by ID */
gid = (gid_t) getlogindefs_num("TTYGROUP", gid);
}
if (fchown(0, uid, gid)) /* tty */
chown_err(cxt->tty_name, uid, gid);
if (fchmod(0, cxt->tty_mode))
chmod_err(cxt->tty_name, cxt->tty_mode);
#ifdef LOGIN_CHOWN_VCS
if (is_consoletty(0)) {
if (chown(cxt->vcsn, uid, gid)) /* vcs */
chown_err(cxt->vcsn, uid, gid);
if (chmod(cxt->vcsn, cxt->tty_mode))
chmod_err(cxt->vcsn, cxt->tty_mode);
if (chown(cxt->vcsan, uid, gid)) /* vcsa */
chown_err(cxt->vcsan, uid, gid);
if (chmod(cxt->vcsan, cxt->tty_mode))
chmod_err(cxt->vcsan, cxt->tty_mode);
}
#endif
}
/*
* Reads the current terminal path and initializes cxt->tty_* variables.
*/
static void init_tty(struct login_context *cxt)
{
struct stat st;
struct termios tt, ttt;
cxt->tty_mode = (mode_t) getlogindefs_num("TTYPERM", TTY_MODE);
get_terminal_name(&cxt->tty_path, &cxt->tty_name, &cxt->tty_number);
/*
* In case login is suid it was possible to use a hardlink as stdin
* and exploit races for a local root exploit. (Wojciech Purczynski).
*
* More precisely, the problem is ttyn := ttyname(0); ...; chown(ttyn);
* here ttyname() might return "/tmp/x", a hardlink to a pseudotty.
* All of this is a problem only when login is suid, which it isn't.
*/
if (!cxt->tty_path || !*cxt->tty_path ||
lstat(cxt->tty_path, &st) != 0 || !S_ISCHR(st.st_mode) ||
(st.st_nlink > 1 && strncmp(cxt->tty_path, "/dev/", 5)) ||
access(cxt->tty_path, R_OK | W_OK) != 0) {
syslog(LOG_ERR, _("FATAL: bad tty"));
sleepexit(EXIT_FAILURE);
}
#ifdef LOGIN_CHOWN_VCS
if (cxt->tty_number) {
/* find names of Virtual Console devices, for later mode change */
snprintf(cxt->vcsn, sizeof(cxt->vcsn), "/dev/vcs%s", cxt->tty_number);
snprintf(cxt->vcsan, sizeof(cxt->vcsan), "/dev/vcsa%s", cxt->tty_number);
}
#endif
tcgetattr(0, &tt);
ttt = tt;
ttt.c_cflag &= ~HUPCL;
if ((fchown(0, 0, 0) || fchmod(0, cxt->tty_mode)) && errno != EROFS) {
syslog(LOG_ERR, _("FATAL: %s: change permissions failed: %m"),
cxt->tty_path);
sleepexit(EXIT_FAILURE);
}
/* Kill processes left on this tty */
tcsetattr(0, TCSANOW, &ttt);
/*
* Let's close file descriptors before vhangup
* https://lkml.org/lkml/2012/6/5/145
*/
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
signal(SIGHUP, SIG_IGN); /* so vhangup() won't kill us */
vhangup();
signal(SIGHUP, SIG_DFL);
/* open stdin,stdout,stderr to the tty */
open_tty(cxt->tty_path);
/* restore tty modes */
tcsetattr(0, TCSAFLUSH, &tt);
}
/*
* Logs failed login attempts in _PATH_BTMP, if it exists.
* Must be called only with username the name of an actual user.
* The most common login failure is to give password instead of username.
*/
static void log_btmp(struct login_context *cxt)
{
struct utmpx ut;
struct timeval tv;
memset(&ut, 0, sizeof(ut));
str2memcpy(ut.ut_user,
cxt->username ? cxt->username : "(unknown)",
sizeof(ut.ut_user));
if (cxt->tty_number)
str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
if (cxt->tty_name)
str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
gettimeofday(&tv, NULL);
ut.ut_tv.tv_sec = tv.tv_sec;
ut.ut_tv.tv_usec = tv.tv_usec;
ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
ut.ut_pid = cxt->pid;
if (cxt->hostname) {
str2memcpy(ut.ut_host, cxt->hostname, sizeof(ut.ut_host));
if (*cxt->hostaddress)
memcpy(&ut.ut_addr_v6, cxt->hostaddress,
sizeof(ut.ut_addr_v6));
}
updwtmpx(_PATH_BTMP, &ut);
}
#ifdef HAVE_LIBAUDIT
static void log_audit(struct login_context *cxt, int status)
{
int audit_fd;
struct passwd *pwd = cxt->pwd;
audit_fd = audit_open();
if (audit_fd == -1)
return;
if (!pwd && cxt->username)
pwd = getpwnam(cxt->username);
audit_log_acct_message(audit_fd,
AUDIT_USER_LOGIN,
NULL,
"login",
cxt->username ? cxt->username : "(unknown)",
pwd ? pwd->pw_uid : (unsigned int) -1,
cxt->hostname,
NULL,
cxt->tty_name,
status);
close(audit_fd);
}
#else /* !HAVE_LIBAUDIT */
# define log_audit(cxt, status)
#endif /* HAVE_LIBAUDIT */
static void log_lastlog(struct login_context *cxt)
{
struct sigaction sa, oldsa_xfsz;
struct lastlog ll;
time_t t;
int fd;
if (!cxt->pwd)
return;
if (cxt->pwd->pw_uid > (uid_t) getlogindefs_num("LASTLOG_UID_MAX", ULONG_MAX))
return;
/* lastlog is huge on systems with large UIDs, ignore SIGXFSZ */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
sigaction(SIGXFSZ, &sa, &oldsa_xfsz);
fd = open(_PATH_LASTLOG, O_RDWR, 0);
if (fd < 0)
goto done;
if (lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET) == -1)
goto done;
/*
* Print last log message.
*/
if (!cxt->quiet) {
if (read(fd, (char *)&ll, sizeof(ll)) == sizeof(ll) &&
ll.ll_time != 0) {
char time_string[CTIME_BUFSIZ];
time_t ll_time = (time_t) ll.ll_time;
ctime_r(&ll_time, time_string);
printf(_("Last login: %.*s "), 24 - 5, time_string);
if (*ll.ll_host != '\0')
printf(_("from %.*s\n"),
(int)sizeof(ll.ll_host), ll.ll_host);
else
printf(_("on %.*s\n"),
(int)sizeof(ll.ll_line), ll.ll_line);
}
if (lseek(fd, (off_t) cxt->pwd->pw_uid * sizeof(ll), SEEK_SET) == -1)
goto done;
}
memset((char *)&ll, 0, sizeof(ll));
time(&t);
ll.ll_time = t; /* ll_time is always 32bit */
if (cxt->tty_name)
str2memcpy(ll.ll_line, cxt->tty_name, sizeof(ll.ll_line));
if (cxt->hostname)
str2memcpy(ll.ll_host, cxt->hostname, sizeof(ll.ll_host));
if (write_all(fd, (char *)&ll, sizeof(ll)))
warn(_("write lastlog failed"));
done:
if (fd >= 0)
close(fd);
sigaction(SIGXFSZ, &oldsa_xfsz, NULL); /* restore original setting */
}
/*
* Update wtmp and utmp logs.
*/
static void log_utmp(struct login_context *cxt)
{
struct utmpx ut;
struct utmpx *utp;
struct timeval tv;
utmpxname(_PATH_UTMP);
setutxent();
/* Find pid in utmp.
*
* login sometimes overwrites the runlevel entry in /var/run/utmp,
* confusing sysvinit. I added a test for the entry type, and the
* problem was gone. (In a runlevel entry, st_pid is not really a pid
* but some number calculated from the previous and current runlevel.)
* -- Michael Riepe <michael@stud.uni-hannover.de>
*/
while ((utp = getutxent()))
if (utp->ut_pid == cxt->pid
&& utp->ut_type >= INIT_PROCESS
&& utp->ut_type <= DEAD_PROCESS)
break;
/* If we can't find a pre-existing entry by pid, try by line.
* BSD network daemons may rely on this. */
if (utp == NULL && cxt->tty_name) {
setutxent();
ut.ut_type = LOGIN_PROCESS;
str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
utp = getutxline(&ut);
}
/* If we can't find a pre-existing entry by pid and line, try it by id.
* Very stupid telnetd daemons don't set up utmp at all. (kzak) */
if (utp == NULL && cxt->tty_number) {
setutxent();
ut.ut_type = DEAD_PROCESS;
str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
utp = getutxid(&ut);
}
if (utp)
memcpy(&ut, utp, sizeof(ut));
else
/* some gettys/telnetds don't initialize utmp... */
memset(&ut, 0, sizeof(ut));
if (cxt->tty_number && ut.ut_id[0] == 0)
str2memcpy(ut.ut_id, cxt->tty_number, sizeof(ut.ut_id));
if (cxt->username)
str2memcpy(ut.ut_user, cxt->username, sizeof(ut.ut_user));
if (cxt->tty_name)
str2memcpy(ut.ut_line, cxt->tty_name, sizeof(ut.ut_line));
gettimeofday(&tv, NULL);
ut.ut_tv.tv_sec = tv.tv_sec;
ut.ut_tv.tv_usec = tv.tv_usec;