From 378c18e5f024ac5a8aef4cb40d7c9aa9633d144c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 16:30:35 +0200 Subject: Adding upstream version 2.38.1. Signed-off-by: Daniel Baumann --- login-utils/Makemodule.am | 299 +++++++ login-utils/auth.c | 65 ++ login-utils/auth.h | 17 + login-utils/ch-common.c | 34 + login-utils/ch-common.h | 6 + login-utils/chfn.1 | 108 +++ login-utils/chfn.1.adoc | 86 ++ login-utils/chfn.c | 479 +++++++++++ login-utils/chsh.1 | 85 ++ login-utils/chsh.1.adoc | 68 ++ login-utils/chsh.c | 323 ++++++++ login-utils/islocal.c | 111 +++ login-utils/islocal.h | 6 + login-utils/last.1 | 239 ++++++ login-utils/last.1.adoc | 137 ++++ login-utils/last.c | 1091 +++++++++++++++++++++++++ login-utils/lastb.1 | 1 + login-utils/libuser.c | 72 ++ login-utils/libuser.h | 18 + login-utils/login.1 | 223 ++++++ login-utils/login.1.adoc | 179 +++++ login-utils/login.c | 1547 ++++++++++++++++++++++++++++++++++++ login-utils/logindefs.c | 609 ++++++++++++++ login-utils/logindefs.h | 14 + login-utils/lslogins.1 | 216 +++++ login-utils/lslogins.1.adoc | 144 ++++ login-utils/lslogins.c | 1716 ++++++++++++++++++++++++++++++++++++++++ login-utils/meson.build | 75 ++ login-utils/newgrp.1 | 70 ++ login-utils/newgrp.1.adoc | 49 ++ login-utils/newgrp.c | 241 ++++++ login-utils/nologin.8 | 100 +++ login-utils/nologin.8.adoc | 79 ++ login-utils/nologin.c | 111 +++ login-utils/runuser.1 | 281 +++++++ login-utils/runuser.1.adoc | 143 ++++ login-utils/runuser.c | 7 + login-utils/setpwnam.c | 216 +++++ login-utils/setpwnam.h | 33 + login-utils/su-common.c | 1292 ++++++++++++++++++++++++++++++ login-utils/su-common.h | 11 + login-utils/su.1 | 299 +++++++ login-utils/su.1.adoc | 159 ++++ login-utils/su.c | 8 + login-utils/sulogin-consoles.c | 829 +++++++++++++++++++ login-utils/sulogin-consoles.h | 55 ++ login-utils/sulogin.8 | 89 +++ login-utils/sulogin.8.adoc | 80 ++ login-utils/sulogin.c | 1191 ++++++++++++++++++++++++++++ login-utils/utmpdump.1 | 100 +++ login-utils/utmpdump.1.adoc | 88 +++ login-utils/utmpdump.c | 414 ++++++++++ login-utils/vigr.8 | 1 + login-utils/vipw.8 | 71 ++ login-utils/vipw.8.adoc | 81 ++ login-utils/vipw.c | 369 +++++++++ 56 files changed, 14435 insertions(+) create mode 100644 login-utils/Makemodule.am create mode 100644 login-utils/auth.c create mode 100644 login-utils/auth.h create mode 100644 login-utils/ch-common.c create mode 100644 login-utils/ch-common.h create mode 100644 login-utils/chfn.1 create mode 100644 login-utils/chfn.1.adoc create mode 100644 login-utils/chfn.c create mode 100644 login-utils/chsh.1 create mode 100644 login-utils/chsh.1.adoc create mode 100644 login-utils/chsh.c create mode 100644 login-utils/islocal.c create mode 100644 login-utils/islocal.h create mode 100644 login-utils/last.1 create mode 100644 login-utils/last.1.adoc create mode 100644 login-utils/last.c create mode 100644 login-utils/lastb.1 create mode 100644 login-utils/libuser.c create mode 100644 login-utils/libuser.h create mode 100644 login-utils/login.1 create mode 100644 login-utils/login.1.adoc create mode 100644 login-utils/login.c create mode 100644 login-utils/logindefs.c create mode 100644 login-utils/logindefs.h create mode 100644 login-utils/lslogins.1 create mode 100644 login-utils/lslogins.1.adoc create mode 100644 login-utils/lslogins.c create mode 100644 login-utils/meson.build create mode 100644 login-utils/newgrp.1 create mode 100644 login-utils/newgrp.1.adoc create mode 100644 login-utils/newgrp.c create mode 100644 login-utils/nologin.8 create mode 100644 login-utils/nologin.8.adoc create mode 100644 login-utils/nologin.c create mode 100644 login-utils/runuser.1 create mode 100644 login-utils/runuser.1.adoc create mode 100644 login-utils/runuser.c create mode 100644 login-utils/setpwnam.c create mode 100644 login-utils/setpwnam.h create mode 100644 login-utils/su-common.c create mode 100644 login-utils/su-common.h create mode 100644 login-utils/su.1 create mode 100644 login-utils/su.1.adoc create mode 100644 login-utils/su.c create mode 100644 login-utils/sulogin-consoles.c create mode 100644 login-utils/sulogin-consoles.h create mode 100644 login-utils/sulogin.8 create mode 100644 login-utils/sulogin.8.adoc create mode 100644 login-utils/sulogin.c create mode 100644 login-utils/utmpdump.1 create mode 100644 login-utils/utmpdump.1.adoc create mode 100644 login-utils/utmpdump.c create mode 100644 login-utils/vigr.8 create mode 100644 login-utils/vipw.8 create mode 100644 login-utils/vipw.8.adoc create mode 100644 login-utils/vipw.c (limited to 'login-utils') diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am new file mode 100644 index 0000000..2d0547a --- /dev/null +++ b/login-utils/Makemodule.am @@ -0,0 +1,299 @@ + +if BUILD_LAST +usrbin_exec_PROGRAMS += last +MANPAGES += login-utils/last.1 +dist_noinst_DATA += login-utils/last.1.adoc +MANLINKS += login-utils/lastb.1 +last_SOURCES = login-utils/last.c lib/monotonic.c +last_LDADD = $(LDADD) libcommon.la $(REALTIME_LIBS) + +install-exec-hook-last: + cd $(DESTDIR)$(usrbin_execdir) && ln -sf last lastb + +INSTALL_EXEC_HOOKS += install-exec-hook-last + +if FUZZING_ENGINE +check_PROGRAMS += test_last_fuzz + +# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements +nodist_EXTRA_test_last_fuzz_SOURCES = dummy.cxx + +test_last_fuzz_SOURCES = login-utils/last.c +test_last_fuzz_CFLAGS = $(AM_CFLAGS) -DFUZZ_TARGET +test_last_fuzz_LDFLAGS = -lpthread +test_last_fuzz_LDADD = $(LDADD) libcommon.la $(LIB_FUZZING_ENGINE) +endif + +endif + +if BUILD_SULOGIN +sbin_PROGRAMS += sulogin +MANPAGES += login-utils/sulogin.8 +dist_noinst_DATA += login-utils/sulogin.8.adoc +sulogin_SOURCES = \ + login-utils/sulogin.c \ + login-utils/sulogin-consoles.c \ + login-utils/sulogin-consoles.h +if USE_PLYMOUTH_SUPPORT +sulogin_SOURCES += lib/plymouth-ctrl.c +endif +sulogin_LDADD = $(LDADD) libcommon.la + +if HAVE_LIBCRYPT +sulogin_LDADD += -lcrypt +endif +if HAVE_SELINUX +sulogin_LDADD += -lselinux +endif + +check_PROGRAMS += test_consoles +test_consoles_SOURCES = login-utils/sulogin-consoles.c +test_consoles_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM +test_consoles_LDADD = $(LDADD) libcommon.la +endif # BUILD_SULOGIN + + +if BUILD_LOGIN +bin_PROGRAMS += login +MANPAGES += login-utils/login.1 +dist_noinst_DATA += login-utils/login.1.adoc +login_SOURCES = \ + login-utils/login.c \ + login-utils/logindefs.c \ + login-utils/logindefs.h +login_LDADD = $(LDADD) libcommon.la -lpam +if HAVE_LINUXPAM +login_LDADD += -lpam_misc +endif +if HAVE_AUDIT +login_LDADD += -laudit +endif +if HAVE_SELINUX +login_LDADD += -lselinux +endif +if HAVE_ECONF +login_LDADD += -leconf +endif +endif # BUILD_LOGIN + + +if BUILD_NOLOGIN +sbin_PROGRAMS += nologin +MANPAGES += login-utils/nologin.8 +dist_noinst_DATA += login-utils/nologin.8.adoc +nologin_SOURCES = login-utils/nologin.c +nologin_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_UTMPDUMP +usrbin_exec_PROGRAMS += utmpdump +MANPAGES += login-utils/utmpdump.1 +dist_noinst_DATA += login-utils/utmpdump.1.adoc +utmpdump_SOURCES = login-utils/utmpdump.c +utmpdump_LDADD = $(LDADD) libcommon.la +endif + + +if BUILD_CHFN_CHSH +usrbin_exec_PROGRAMS += chfn chsh +MANPAGES += \ + login-utils/chfn.1 \ + login-utils/chsh.1 +dist_noinst_DATA += \ + login-utils/chfn.1.adoc \ + login-utils/chsh.1.adoc + +chfn_chsh_sources = \ + login-utils/ch-common.h \ + login-utils/ch-common.c +chfn_chsh_cflags = $(SUID_CFLAGS) $(AM_CFLAGS) +chfn_chsh_ldflags = $(SUID_LDFLAGS) $(AM_LDFLAGS) +chfn_chsh_ldadd = libcommon.la + +if CHFN_CHSH_PASSWORD +chfn_chsh_ldadd += -lpam +if HAVE_LINUXPAM +chfn_chsh_ldadd += -lpam_misc +endif +chfn_chsh_sources += \ + login-utils/auth.c \ + login-utils/auth.h +endif # CHFN_CHSH_PASSWORD + +if HAVE_USER +chfn_chsh_ldflags += $(LIBUSER_LIBS) +chfn_chsh_cflags += $(LIBUSER_CFLAGS) +chfn_chsh_sources+= \ + login-utils/libuser.c \ + login-utils/libuser.h +else +chfn_chsh_sources += \ + login-utils/islocal.c \ + login-utils/islocal.h \ + login-utils/setpwnam.c \ + login-utils/setpwnam.h +endif + +if HAVE_SELINUX +chfn_chsh_sources += \ + lib/selinux-utils.c \ + include/selinux-utils.h +chfn_chsh_ldadd += -lselinux +endif + +chfn_SOURCES = \ + login-utils/chfn.c \ + login-utils/logindefs.c \ + login-utils/logindefs.h \ + $(chfn_chsh_sources) +chfn_CFLAGS = $(chfn_chsh_cflags) +chfn_LDFLAGS = $(chfn_chsh_ldflags) +chfn_LDADD = $(LDADD) $(chfn_chsh_ldadd) +if HAVE_ECONF +chfn_LDADD += -leconf +endif + +chsh_SOURCES = login-utils/chsh.c $(chfn_chsh_sources) +chsh_CFLAGS = $(chfn_chsh_cflags) +chsh_LDFLAGS = $(chfn_chsh_ldflags) +chsh_LDADD = $(LDADD) $(chfn_chsh_ldadd) +endif # BUILD_CHFN_CHSH + + +if BUILD_SU +bin_PROGRAMS += su +MANPAGES += login-utils/su.1 +dist_noinst_DATA += login-utils/su.1.adoc +su_SOURCES = \ + login-utils/su.c \ + login-utils/su-common.c \ + login-utils/su-common.h \ + login-utils/logindefs.c \ + login-utils/logindefs.h +su_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS) +su_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS) +su_LDADD = $(LDADD) libcommon.la -lpam +if HAVE_LINUXPAM +su_LDADD += -lpam_misc +endif +if HAVE_PTY +su_SOURCES += lib/pty-session.c \ + include/pty-session.h \ + lib/monotonic.c +su_LDADD += -lutil $(REALTIME_LIBS) +endif +if HAVE_ECONF +su_LDADD += -leconf +endif +endif # BUILD_SU + + +if BUILD_RUNUSER +sbin_PROGRAMS += runuser +MANPAGES += login-utils/runuser.1 +dist_noinst_DATA += login-utils/runuser.1.adoc +runuser_SOURCES = \ + login-utils/runuser.c \ + login-utils/su-common.c \ + login-utils/su-common.h \ + login-utils/logindefs.c \ + login-utils/logindefs.h +runuser_LDADD = $(LDADD) libcommon.la -lpam +if HAVE_LINUXPAM +runuser_LDADD += -lpam_misc +endif +if HAVE_PTY +runuser_SOURCES += lib/pty-session.c \ + include/pty-session.h \ + lib/monotonic.c +runuser_LDADD += -lutil $(REALTIME_LIBS) +endif +if HAVE_ECONF +runuser_LDADD += -leconf +endif +endif # BUILD_RUNUSER + + +if BUILD_NEWGRP +usrbin_exec_PROGRAMS += newgrp +MANPAGES += login-utils/newgrp.1 +dist_noinst_DATA += login-utils/newgrp.1.adoc +newgrp_SOURCES = login-utils/newgrp.c +newgrp_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS) +newgrp_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS) +newgrp_LDADD = $(LDADD) +if HAVE_LIBCRYPT +newgrp_LDADD += -lcrypt +endif +endif # BUILD_NEWGRP + + +if BUILD_LSLOGINS +usrbin_exec_PROGRAMS += lslogins +MANPAGES += login-utils/lslogins.1 +dist_noinst_DATA += login-utils/lslogins.1.adoc +lslogins_SOURCES = \ + login-utils/lslogins.c \ + login-utils/logindefs.c \ + login-utils/logindefs.h +lslogins_LDADD = $(LDADD) libcommon.la libsmartcols.la +lslogins_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +if HAVE_SELINUX +lslogins_LDADD += -lselinux +endif +if HAVE_SYSTEMD +lslogins_LDADD += $(SYSTEMD_LIBS) $(SYSTEMD_JOURNAL_LIBS) +lslogins_CFLAGS += $(SYSTEMD_CFLAGS) $(SYSTEMD_JOURNAL_CFLAGS) +endif +if HAVE_ECONF +lslogins_LDADD += -leconf +endif +endif # BUILD_LSLOGINS + + +if BUILD_VIPW +usrsbin_exec_PROGRAMS += vipw +MANPAGES += login-utils/vipw.8 +dist_noinst_DATA += login-utils/vipw.8.adoc +MANLINKS += login-utils/vigr.8 +vipw_SOURCES = \ + login-utils/vipw.c \ + login-utils/setpwnam.h +vipw_LDADD = $(LDADD) libcommon.la +if HAVE_SELINUX +vipw_LDADD += -lselinux +endif +install-exec-hook-vipw:: + cd $(DESTDIR)$(usrsbin_execdir) && ln -sf vipw vigr + +INSTALL_EXEC_HOOKS += install-exec-hook-vipw +endif # BUILD_VIPW + + +check_PROGRAMS += \ + test_islocal \ + test_logindefs + +test_islocal_SOURCES = login-utils/islocal.c +test_islocal_CPPFLAGS = -DTEST_PROGRAM $(AM_CPPFLAGS) + +test_logindefs_SOURCES = \ + login-utils/logindefs.c \ + login-utils/logindefs.h +test_logindefs_CPPFLAGS = -DTEST_PROGRAM $(AM_CPPFLAGS) +test_logindefs_LDADD = $(LDADD) libcommon.la +if HAVE_ECONF +test_logindefs_LDADD += -leconf +endif + + +install-exec-hook: +if BUILD_SU +if MAKEINSTALL_DO_CHOWN + chown root:root $(DESTDIR)$(bindir)/su +endif +if MAKEINSTALL_DO_SETUID + chmod 4755 $(DESTDIR)$(bindir)/su +endif +endif diff --git a/login-utils/auth.c b/login-utils/auth.c new file mode 100644 index 0000000..fdeb12b --- /dev/null +++ b/login-utils/auth.c @@ -0,0 +1,65 @@ +/* + * auth.c -- PAM authorization code, common between chsh and chfn + * (c) 2012 by Cody Maloney + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + * + */ + +#include +#ifdef HAVE_SECURITY_PAM_MISC_H +# include +#elif defined(HAVE_SECURITY_OPENPAM_H) +# include +#endif + +#include "c.h" +#include "auth.h" + +static int pam_fail_check(pam_handle_t *pamh, int retcode) +{ + if (retcode == PAM_SUCCESS) + return 0; + warnx("%s", pam_strerror(pamh, retcode)); + pam_end(pamh, retcode); + return 1; +} + +int auth_pam(const char *service_name, uid_t uid, const char *username) +{ + if (uid != 0) { + pam_handle_t *pamh = NULL; +#ifdef HAVE_SECURITY_PAM_MISC_H + struct pam_conv conv = { misc_conv, NULL }; +#elif defined(HAVE_SECURITY_OPENPAM_H) + struct pam_conv conv = { openpam_ttyconv, NULL }; +#endif + int retcode; + + retcode = pam_start(service_name, username, &conv, &pamh); + if (pam_fail_check(pamh, retcode)) + return FALSE; + + retcode = pam_authenticate(pamh, 0); + if (pam_fail_check(pamh, retcode)) + return FALSE; + + retcode = pam_acct_mgmt(pamh, 0); + if (retcode == PAM_NEW_AUTHTOK_REQD) + retcode = + pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + if (pam_fail_check(pamh, retcode)) + return FALSE; + + retcode = pam_setcred(pamh, 0); + if (pam_fail_check(pamh, retcode)) + return FALSE; + + pam_end(pamh, 0); + /* no need to establish a session; this isn't a + * session-oriented activity... */ + } + return TRUE; +} diff --git a/login-utils/auth.h b/login-utils/auth.h new file mode 100644 index 0000000..ee58d12 --- /dev/null +++ b/login-utils/auth.h @@ -0,0 +1,17 @@ +/* + * auth.h -- PAM authorization code, common between chsh and chfn + * (c) 2012 by Cody Maloney + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + * + */ +#ifndef UTIL_LINUX_LOGIN_AUTH_H +#define UTIL_LINUX_LOGIN_AUTH_H + +#include + +extern int auth_pam(const char *service_name, uid_t uid, const char *username); + +#endif /* UTIL_LINUX_LOGIN_AUTH_H */ diff --git a/login-utils/ch-common.c b/login-utils/ch-common.c new file mode 100644 index 0000000..34b09f3 --- /dev/null +++ b/login-utils/ch-common.c @@ -0,0 +1,34 @@ +/* + * chfn and chsh shared functions + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + */ + +#include +#include + +#include "c.h" +#include "nls.h" + +#include "ch-common.h" + +/* + * illegal_passwd_chars () - + * check whether a string contains illegal characters + */ +int illegal_passwd_chars(const char *str) +{ + const char illegal[] = ",:=\"\n"; + const size_t len = strlen(str); + size_t i; + + if (strpbrk(str, illegal)) + return 1; + for (i = 0; i < len; i++) { + if (iscntrl(str[i])) + return 1; + } + return 0; +} diff --git a/login-utils/ch-common.h b/login-utils/ch-common.h new file mode 100644 index 0000000..7f70e50 --- /dev/null +++ b/login-utils/ch-common.h @@ -0,0 +1,6 @@ +#ifndef UTIL_LINUX_CH_COMMON_H +#define UTIL_LINUX_CH_COMMON_H + +extern int illegal_passwd_chars(const char *str); + +#endif /* UTIL_LINUX_CH_COMMON */ diff --git a/login-utils/chfn.1 b/login-utils/chfn.1 new file mode 100644 index 0000000..bf574ff --- /dev/null +++ b/login-utils/chfn.1 @@ -0,0 +1,108 @@ +'\" t +.\" Title: chfn +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "CHFN" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +chfn \- change your finger information +.SH "SYNOPSIS" +.sp +\fBchfn\fP [\fB\-f\fP \fIfull\-name\fP] [\fB\-o\fP \fIoffice\fP] [\fB\-p\fP \fIoffice\-phone\fP] [\fB\-h\fP \fIhome\-phone\fP] [\fB\-u\fP] [\fB\-v\fP] [\fIusername\fP] +.SH "DESCRIPTION" +.sp +\fBchfn\fP is used to change your finger information. This information is stored in the \fI/etc/passwd\fP file, and is displayed by the \fBfinger\fP program. The Linux \fBfinger\fP command will display four pieces of information that can be changed by \fBchfn\fP: your real name, your work room and phone, and your home phone. +.sp +Any of the four pieces of information can be specified on the command line. If no information is given on the command line, \fBchfn\fP enters interactive mode. +.sp +In interactive mode, \fBchfn\fP will prompt for each field. At a prompt, you can enter the new information, or just press return to leave the field unchanged. Enter the keyword "none" to make the field blank. +.sp +\fBchfn\fP supports non\-local entries (kerberos, LDAP, etc.) if linked with libuser, otherwise use \fBypchfn\fP(1), \fBlchfn\fP(1) or any other implementation for non\-local entries. +.SH "OPTIONS" +.sp +\fB\-f\fP, \fB\-\-full\-name\fP \fIfull\-name\fP +.RS 4 +Specify your real name. +.RE +.sp +\fB\-o\fP, \fB\-\-office\fP \fIoffice\fP +.RS 4 +Specify your office room number. +.RE +.sp +\fB\-p\fP, \fB\-\-office\-phone\fP \fIoffice\-phone\fP +.RS 4 +Specify your office phone number. +.RE +.sp +\fB\-h\fP, \fB\-\-home\-phone\fP \fIhome\-phone\fP +.RS 4 +Specify your home phone number. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "CONFIG FILE ITEMS" +.sp +\fBchfn\fP reads the \fI/etc/login.defs\fP configuration file (see \fBlogin.defs\fP(5)). Note that the configuration file could be distributed with another package (e.g., shadow\-utils). The following configuration items are relevant for \fBchfn\fP: +.sp +\fBCHFN_RESTRICT\fP \fIstring\fP +.RS 4 +Indicate which fields are changeable by \fBchfn\fP. +.sp +The boolean setting \fB"yes"\fP means that only the Office, Office Phone and Home Phone fields are changeable, and boolean setting \fB"no"\fP means that also the Full Name is changeable. +.sp +Another way to specify changeable fields is by abbreviations: f = Full Name, r = Office (room), w = Office (work) Phone, h = Home Phone. For example, \fBCHFN_RESTRICT "wh"\fP allows changing work and home phone numbers. +.sp +If \fBCHFN_RESTRICT\fP is undefined, then all finger information is read\-only. This is the default. +.RE +.SH "EXIT STATUS" +.sp +Returns 0 if operation was successful, 1 if operation failed or command syntax was not valid. +.SH "AUTHORS" +.sp +.MTO "svalente\(atmit.edu" "Salvatore Valente" "" +.SH "SEE ALSO" +.sp +\fBchsh\fP(1), +\fBfinger\fP(1), +\fBlogin.defs\fP(5), +\fBpasswd\fP(5) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBchfn\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file diff --git a/login-utils/chfn.1.adoc b/login-utils/chfn.1.adoc new file mode 100644 index 0000000..6377e26 --- /dev/null +++ b/login-utils/chfn.1.adoc @@ -0,0 +1,86 @@ +//po4a: entry man manual +//// +chfn.1 -- change your finger information +(c) 1994 by salvatore valente + +This program is free software. You can redistribute it and +modify it under the terms of the GNU General Public License. +There is no warranty. +//// += chfn(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: chfn + +== NAME + +chfn - change your finger information + +== SYNOPSIS + +*chfn* [*-f* _full-name_] [*-o* _office_] [*-p* _office-phone_] [*-h* _home-phone_] [*-u*] [*-v*] [_username_] + +== DESCRIPTION + +*chfn* is used to change your finger information. This information is stored in the _/etc/passwd_ file, and is displayed by the *finger* program. The Linux *finger* command will display four pieces of information that can be changed by *chfn*: your real name, your work room and phone, and your home phone. + +Any of the four pieces of information can be specified on the command line. If no information is given on the command line, *chfn* enters interactive mode. + +In interactive mode, *chfn* will prompt for each field. At a prompt, you can enter the new information, or just press return to leave the field unchanged. Enter the keyword "none" to make the field blank. + +*chfn* supports non-local entries (kerberos, LDAP, etc.) if linked with libuser, otherwise use *ypchfn*(1), *lchfn*(1) or any other implementation for non-local entries. + +== OPTIONS + +*-f*, *--full-name* _full-name_:: +Specify your real name. + +*-o*, *--office* _office_:: +Specify your office room number. + +*-p*, *--office-phone* _office-phone_:: +Specify your office phone number. + +*-h*, *--home-phone* _home-phone_:: +Specify your home phone number. + +include::man-common/help-version.adoc[] + +== CONFIG FILE ITEMS + +*chfn* reads the _/etc/login.defs_ configuration file (see *login.defs*(5)). Note that the configuration file could be distributed with another package (e.g., shadow-utils). The following configuration items are relevant for *chfn*: + +*CHFN_RESTRICT* _string_:: + +Indicate which fields are changeable by *chfn*. ++ +The boolean setting *"yes"* means that only the Office, Office Phone and Home Phone fields are changeable, and boolean setting *"no"* means that also the Full Name is changeable. ++ +Another way to specify changeable fields is by abbreviations: f = Full Name, r = Office (room), w = Office (work) Phone, h = Home Phone. For example, *CHFN_RESTRICT "wh"* allows changing work and home phone numbers. ++ +If *CHFN_RESTRICT* is undefined, then all finger information is read-only. This is the default. + +== EXIT STATUS + +Returns 0 if operation was successful, 1 if operation failed or command syntax was not valid. + +== AUTHORS + +mailto:svalente@mit.edu[Salvatore Valente] + +== SEE ALSO + +*chsh*(1), +*finger*(1), +*login.defs*(5), +*passwd*(5) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/login-utils/chfn.c b/login-utils/chfn.c new file mode 100644 index 0000000..8f00d15 --- /dev/null +++ b/login-utils/chfn.c @@ -0,0 +1,479 @@ +/* + * chfn.c -- change your finger information + * (c) 1994 by salvatore valente + * (c) 2012 by Cody Maloney + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + * + * $Author: aebr $ + * $Revision: 1.18 $ + * $Date: 1998/06/11 22:30:11 $ + * + * Updated Thu Oct 12 09:19:26 1995 by faith@cs.unc.edu with security + * patches from Zefram + * + * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de, + * to remove trailing empty fields. Oct 5, 96. + * + * 1999-02-22 Arkadiusz Miśkiewicz + * - added Native Language Support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "env.h" +#include "closestream.h" +#include "islocal.h" +#include "nls.h" +#include "setpwnam.h" +#include "strutils.h" +#include "xalloc.h" +#include "logindefs.h" + +#include "ch-common.h" + +#ifdef HAVE_LIBSELINUX +# include +# include "selinux-utils.h" +#endif + +#ifdef HAVE_LIBUSER +# include +# include "libuser.h" +#elif CHFN_CHSH_PASSWORD +# include "auth.h" +#endif + +struct finfo { + char *full_name; + char *office; + char *office_phone; + char *home_phone; + char *other; +}; + +struct chfn_control { + struct passwd *pw; + char *username; + /* "oldf" Contains the users original finger information. + * "newf" Contains the changed finger information, and contains + * NULL in fields that haven't been changed. + * In the end, "newf" is folded into "oldf". */ + struct finfo oldf, newf; + unsigned int + allow_fullname:1, /* The login.defs restriction */ + allow_room:1, /* see: man login.defs(5) */ + allow_work:1, /* and look for CHFN_RESTRICT */ + allow_home:1, /* keyword for these four. */ + changed:1, /* is change requested */ + interactive:1; /* whether to prompt for fields or not */ +}; + +/* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */ +#define MAX_FIELD_SIZE 256 + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *fp = stdout; + fputs(USAGE_HEADER, fp); + fprintf(fp, _(" %s [options] []\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, fp); + fputs(_("Change your finger information.\n"), fp); + + fputs(USAGE_OPTIONS, fp); + fputs(_(" -f, --full-name real name\n"), fp); + fputs(_(" -o, --office office number\n"), fp); + fputs(_(" -p, --office-phone office phone number\n"), fp); + fputs(_(" -h, --home-phone home phone number\n"), fp); + fputs(USAGE_SEPARATOR, fp); + printf( " -u, --help %s\n", USAGE_OPTSTR_HELP); + printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION); + printf(USAGE_MAN_TAIL("chfn(1)")); + exit(EXIT_SUCCESS); +} + +/* + * check_gecos_string () -- + * check that the given gecos string is legal. if it's not legal, + * output "msg" followed by a description of the problem, and return (-1). + */ +static int check_gecos_string(const char *msg, char *gecos) +{ + const size_t len = strlen(gecos); + + if (MAX_FIELD_SIZE < len) { + warnx(_("field %s is too long"), msg); + return -1; + } + if (illegal_passwd_chars(gecos)) { + warnx(_("%s: has illegal characters"), gecos); + return -1; + } + return 0; +} + +/* + * parse_argv () -- + * parse the command line arguments. + * returns true if no information beyond the username was given. + */ +static void parse_argv(struct chfn_control *ctl, int argc, char **argv) +{ + int index, c, status = 0; + static const struct option long_options[] = { + { "full-name", required_argument, NULL, 'f' }, + { "office", required_argument, NULL, 'o' }, + { "office-phone", required_argument, NULL, 'p' }, + { "home-phone", required_argument, NULL, 'h' }, + { "help", no_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 }, + }; + + while ((c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options, + &index)) != -1) { + switch (c) { + case 'f': + if (!ctl->allow_fullname) + errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Name")); + ctl->newf.full_name = optarg; + status += check_gecos_string(_("Name"), optarg); + break; + case 'o': + if (!ctl->allow_room) + errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office")); + ctl->newf.office = optarg; + status += check_gecos_string(_("Office"), optarg); + break; + case 'p': + if (!ctl->allow_work) + errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office Phone")); + ctl->newf.office_phone = optarg; + status += check_gecos_string(_("Office Phone"), optarg); + break; + case 'h': + if (!ctl->allow_home) + errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Home Phone")); + ctl->newf.home_phone = optarg; + status += check_gecos_string(_("Home Phone"), optarg); + break; + case 'v': + print_version(EXIT_SUCCESS); + case 'u': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + ctl->changed = 1; + ctl->interactive = 0; + } + if (status != 0) + exit(EXIT_FAILURE); + /* done parsing arguments. check for a username. */ + if (optind < argc) { + if (optind + 1 < argc) { + warnx(_("cannot handle multiple usernames")); + errtryhelp(EXIT_FAILURE); + } + ctl->username = argv[optind]; + } +} + +/* + * parse_passwd () -- + * take a struct password and fill in the fields of the struct finfo. + */ +static void parse_passwd(struct chfn_control *ctl) +{ + char *gecos; + + if (!ctl->pw) + return; + /* use pw_gecos - we take a copy since PAM destroys the original */ + gecos = xstrdup(ctl->pw->pw_gecos); + /* extract known fields */ + ctl->oldf.full_name = strsep(&gecos, ","); + ctl->oldf.office = strsep(&gecos, ","); + ctl->oldf.office_phone = strsep(&gecos, ","); + ctl->oldf.home_phone = strsep(&gecos, ","); + /* extra fields contain site-specific information, and can + * not be changed by this version of chfn. */ + ctl->oldf.other = strsep(&gecos, ","); +} + +/* + * ask_new_field () -- + * ask the user for a given field and check that the string is legal. + */ +static char *ask_new_field(struct chfn_control *ctl, const char *question, + char *def_val) +{ + int len; + char *buf = NULL; /* leave initialized to NULL or getline segfaults */ + size_t dummy = 0; + + if (!def_val) + def_val = ""; + + while (true) { + printf("%s [%s]:", question, def_val); + __fpurge(stdin); + + putchar(' '); + fflush(stdout); + + if (getline(&buf, &dummy, stdin) < 0) + errx(EXIT_FAILURE, _("Aborted.")); + + /* remove white spaces from string end */ + ltrim_whitespace((unsigned char *) buf); + len = rtrim_whitespace((unsigned char *) buf); + if (len == 0) { + free(buf); + return xstrdup(def_val); + } + if (!strcasecmp(buf, "none")) { + free(buf); + ctl->changed = 1; + return xstrdup(""); + } + if (check_gecos_string(question, buf) >= 0) + break; + } + ctl->changed = 1; + return buf; +} + +/* + * get_login_defs() + * find /etc/login.defs CHFN_RESTRICT and save restrictions to run time + */ +static void get_login_defs(struct chfn_control *ctl) +{ + const char *s; + size_t i; + int broken = 0; + + /* real root does not have restrictions */ + if (geteuid() == getuid() && getuid() == 0) { + ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1; + return; + } + s = getlogindefs_str("CHFN_RESTRICT", ""); + if (!strcmp(s, "yes")) { + ctl->allow_room = ctl->allow_work = ctl->allow_home = 1; + return; + } + if (!strcmp(s, "no")) { + ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1; + return; + } + for (i = 0; s[i]; i++) { + switch (s[i]) { + case 'f': + ctl->allow_fullname = 1; + break; + case 'r': + ctl->allow_room = 1; + break; + case 'w': + ctl->allow_work = 1; + break; + case 'h': + ctl->allow_home = 1; + break; + default: + broken = 1; + } + } + if (broken) + warnx(_("%s: CHFN_RESTRICT has unexpected value: %s"), _PATH_LOGINDEFS, s); + if (!ctl->allow_fullname && !ctl->allow_room && !ctl->allow_work && !ctl->allow_home) + errx(EXIT_FAILURE, _("%s: CHFN_RESTRICT does not allow any changes"), _PATH_LOGINDEFS); +} + +/* + * ask_info () -- + * prompt the user for the finger information and store it. + */ +static void ask_info(struct chfn_control *ctl) +{ + if (ctl->allow_fullname) + ctl->newf.full_name = ask_new_field(ctl, _("Name"), ctl->oldf.full_name); + if (ctl->allow_room) + ctl->newf.office = ask_new_field(ctl, _("Office"), ctl->oldf.office); + if (ctl->allow_work) + ctl->newf.office_phone = ask_new_field(ctl, _("Office Phone"), ctl->oldf.office_phone); + if (ctl->allow_home) + ctl->newf.home_phone = ask_new_field(ctl, _("Home Phone"), ctl->oldf.home_phone); + putchar('\n'); +} + +/* + * find_field () -- + * find field value in uninteractive mode; can be new, old, or blank + */ +static char *find_field(char *nf, char *of) +{ + if (nf) + return nf; + if (of) + return of; + return xstrdup(""); +} + +/* + * add_missing () -- + * add not supplied field values when in uninteractive mode + */ +static void add_missing(struct chfn_control *ctl) +{ + ctl->newf.full_name = find_field(ctl->newf.full_name, ctl->oldf.full_name); + ctl->newf.office = find_field(ctl->newf.office, ctl->oldf.office); + ctl->newf.office_phone = find_field(ctl->newf.office_phone, ctl->oldf.office_phone); + ctl->newf.home_phone = find_field(ctl->newf.home_phone, ctl->oldf.home_phone); + ctl->newf.other = find_field(ctl->newf.other, ctl->oldf.other); + printf("\n"); +} + +/* + * save_new_data () -- + * save the given finger info in /etc/passwd. + * return zero on success. + */ +static int save_new_data(struct chfn_control *ctl) +{ + char *gecos; + int len; + + /* create the new gecos string */ + len = xasprintf(&gecos, "%s,%s,%s,%s,%s", + ctl->newf.full_name, + ctl->newf.office, + ctl->newf.office_phone, + ctl->newf.home_phone, + ctl->newf.other); + + /* remove trailing empty fields (but not subfields of ctl->newf.other) */ + if (!ctl->newf.other || !*ctl->newf.other) { + while (len > 0 && gecos[len - 1] == ',') + len--; + gecos[len] = 0; + } + +#ifdef HAVE_LIBUSER + if (set_value_libuser("chfn", ctl->username, ctl->pw->pw_uid, + LU_GECOS, gecos) < 0) { +#else /* HAVE_LIBUSER */ + /* write the new struct passwd to the passwd file. */ + ctl->pw->pw_gecos = gecos; + if (setpwnam(ctl->pw, ".chfn") < 0) { + warn("setpwnam failed"); +#endif + printf(_ + ("Finger information *NOT* changed. Try again later.\n")); + return -1; + } + free(gecos); + printf(_("Finger information changed.\n")); + return 0; +} + +int main(int argc, char **argv) +{ + uid_t uid; + struct chfn_control ctl = { + .interactive = 1 + }; + + sanitize_env(); + setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */ + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + uid = getuid(); + + /* check /etc/login.defs CHFN_RESTRICT */ + get_login_defs(&ctl); + + parse_argv(&ctl, argc, argv); + if (!ctl.username) { + ctl.pw = getpwuid(uid); + if (!ctl.pw) + errx(EXIT_FAILURE, _("you (user %d) don't exist."), + uid); + ctl.username = ctl.pw->pw_name; + } else { + ctl.pw = getpwnam(ctl.username); + if (!ctl.pw) + errx(EXIT_FAILURE, _("user \"%s\" does not exist."), + ctl.username); + } + parse_passwd(&ctl); +#ifndef HAVE_LIBUSER + if (!(is_local(ctl.username))) + errx(EXIT_FAILURE, _("can only change local entries")); +#endif + +#ifdef HAVE_LIBSELINUX + if (is_selinux_enabled() > 0) { + char *user_cxt = NULL; + + if (uid == 0 && !ul_selinux_has_access("passwd", "chfn", &user_cxt)) + errx(EXIT_FAILURE, + _("%s is not authorized to change " + "the finger info of %s"), + user_cxt ? : _("Unknown user context"), + ctl.username); + + if (ul_setfscreatecon_from_file(_PATH_PASSWD) != 0) + errx(EXIT_FAILURE, + _("can't set default context for %s"), _PATH_PASSWD); + } +#endif + +#ifdef HAVE_LIBUSER + /* If we're setuid and not really root, disallow the password change. */ + if (geteuid() != getuid() && uid != ctl.pw->pw_uid) { +#else + if (uid != 0 && uid != ctl.pw->pw_uid) { +#endif + errno = EACCES; + err(EXIT_FAILURE, _("running UID doesn't match UID of user we're " + "altering, change denied")); + } + + printf(_("Changing finger information for %s.\n"), ctl.username); + +#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD) + if (!auth_pam("chfn", uid, ctl.username)) { + return EXIT_FAILURE; + } +#endif + + if (ctl.interactive) + ask_info(&ctl); + + add_missing(&ctl); + + if (!ctl.changed) { + printf(_("Finger information not changed.\n")); + return EXIT_SUCCESS; + } + + return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/login-utils/chsh.1 b/login-utils/chsh.1 new file mode 100644 index 0000000..0f7d97d --- /dev/null +++ b/login-utils/chsh.1 @@ -0,0 +1,85 @@ +'\" t +.\" Title: chsh +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "CHSH" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +chsh \- change your login shell +.SH "SYNOPSIS" +.sp +\fBchsh\fP [\fB\-s\fP \fIshell\fP] [\fB\-l\fP] [\fB\-h\fP] [\fB\-v\fP] [\fIusername\fP] +.SH "DESCRIPTION" +.sp +\fBchsh\fP is used to change your login shell. If a shell is not given on the command line, \fBchsh\fP prompts for one. +.sp +\fBchsh\fP supports non\-local entries (kerberos, LDAP, etc.) if linked with libuser, otherwise use \fBypchsh\fP(1), \fBlchsh\fP(1) or any other implementation for non\-local entries. +.SH "OPTIONS" +.sp +\fB\-s\fP, \fB\-\-shell\fP \fIshell\fP +.RS 4 +Specify your login shell. +.RE +.sp +\fB\-l\fP, \fB\-\-list\-shells\fP +.RS 4 +Print the list of shells listed in \fI/etc/shells\fP and exit. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "VALID SHELLS" +.sp +\fBchsh\fP will accept the full pathname of any executable file on the system. +.sp +The default behavior for non\-root users is to accept only shells listed in the \fI/etc/shells\fP file, and issue a warning for root user. It can also be configured at compile\-time to only issue a warning for all users. +.SH "EXIT STATUS" +.sp +Returns 0 if operation was successful, 1 if operation failed or command syntax was not valid. +.SH "AUTHORS" +.sp +.MTO "svalente\(atmit.edu" "Salvatore Valente" "" +.SH "SEE ALSO" +.sp +\fBlogin\fP(1), +\fBlogin.defs\fP(5), +\fBpasswd\fP(5), +\fBshells\fP(5) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBchsh\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file diff --git a/login-utils/chsh.1.adoc b/login-utils/chsh.1.adoc new file mode 100644 index 0000000..939674a --- /dev/null +++ b/login-utils/chsh.1.adoc @@ -0,0 +1,68 @@ +//po4a: entry man manual +//// +chsh.1 -- change your login shell +(c) 1994 by salvatore valente + +This program is free software. You can redistribute it and +modify it under the terms of the GNU General Public License. +There is no warranty. +//// += chsh(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: chsh + +== NAME + +chsh - change your login shell + +== SYNOPSIS + +*chsh* [*-s* _shell_] [*-l*] [*-h*] [*-v*] [_username_] + +== DESCRIPTION + +*chsh* is used to change your login shell. If a shell is not given on the command line, *chsh* prompts for one. + +*chsh* supports non-local entries (kerberos, LDAP, etc.) if linked with libuser, otherwise use *ypchsh*(1), *lchsh*(1) or any other implementation for non-local entries. + +== OPTIONS + +*-s*, *--shell* _shell_:: +Specify your login shell. + +*-l*, *--list-shells*:: +Print the list of shells listed in _/etc/shells_ and exit. + +include::man-common/help-version.adoc[] + +== VALID SHELLS + +*chsh* will accept the full pathname of any executable file on the system. + +The default behavior for non-root users is to accept only shells listed in the _/etc/shells_ file, and issue a warning for root user. It can also be configured at compile-time to only issue a warning for all users. + +== EXIT STATUS + +Returns 0 if operation was successful, 1 if operation failed or command syntax was not valid. + +== AUTHORS + +mailto:svalente@mit.edu[Salvatore Valente] + +== SEE ALSO + +*login*(1), +*login.defs*(5), +*passwd*(5), +*shells*(5) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/login-utils/chsh.c b/login-utils/chsh.c new file mode 100644 index 0000000..b7e7017 --- /dev/null +++ b/login-utils/chsh.c @@ -0,0 +1,323 @@ +/* + * chsh.c -- change your login shell + * (c) 1994 by salvatore valente + * (c) 2012 by Cody Maloney + * + * this program is free software. you can redistribute it and + * modify it under the terms of the gnu general public license. + * there is no warranty. + * + * $Author: aebr $ + * $Revision: 1.19 $ + * $Date: 1998/06/11 22:30:14 $ + * + * Updated Thu Oct 12 09:33:15 1995 by faith@cs.unc.edu with security + * patches from Zefram + * + * Updated Mon Jul 1 18:46:22 1996 by janl@math.uio.no with security + * suggestion from Zefram. Disallowing users with shells not in /etc/shells + * from changing their shell. + * + * 1999-02-22 Arkadiusz Miśkiewicz + * - added Native Language Support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "env.h" +#include "closestream.h" +#include "islocal.h" +#include "nls.h" +#include "pathnames.h" +#include "pwdutils.h" +#include "setpwnam.h" +#include "strutils.h" +#include "xalloc.h" + +#include "ch-common.h" + +#ifdef HAVE_LIBSELINUX +# include +# include "selinux-utils.h" +#endif + +#ifdef HAVE_LIBUSER +# include +# include "libuser.h" +#elif CHFN_CHSH_PASSWORD +# include "auth.h" +#endif + +struct sinfo { + char *username; + char *shell; +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *fp = stdout; + fputs(USAGE_HEADER, fp); + fprintf(fp, _(" %s [options] []\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, fp); + fputs(_("Change your login shell.\n"), fp); + + fputs(USAGE_OPTIONS, fp); + fputs(_(" -s, --shell specify login shell\n"), fp); + fputs(_(" -l, --list-shells print list of shells and exit\n"), fp); + fputs(USAGE_SEPARATOR, fp); + printf( " -u, --help %s\n", USAGE_OPTSTR_HELP); + printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION); + printf(USAGE_MAN_TAIL("chsh(1)")); + exit(EXIT_SUCCESS); +} + +/* + * is_known_shell() -- if the given shell appears in /etc/shells, + * return true. if not, return false. + */ +static int is_known_shell(const char *shell_name) +{ + char *s, ret = 0; + + if (!shell_name) + return 0; + + setusershell(); + while ((s = getusershell())) { + if (strcmp(shell_name, s) == 0) { + ret = 1; + break; + } + } + endusershell(); + return ret; +} + +/* + * print_shells () -- /etc/shells is outputted to stdout. + */ +static void print_shells(void) +{ + char *s; + + while ((s = getusershell())) + printf("%s\n", s); + endusershell(); +} + +/* + * parse_argv () -- + * parse the command line arguments, and fill in "pinfo" with any + * information from the command line. + */ +static void parse_argv(int argc, char **argv, struct sinfo *pinfo) +{ + static const struct option long_options[] = { + {"shell", required_argument, NULL, 's'}, + {"list-shells", no_argument, NULL, 'l'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0}, + }; + int c; + + while ((c = getopt_long(argc, argv, "s:lhuv", long_options, NULL)) != -1) { + switch (c) { + case 'v': + print_version(EXIT_SUCCESS); + case 'u': /* deprecated */ + case 'h': + usage(); + case 'l': + print_shells(); + exit(EXIT_SUCCESS); + case 's': + pinfo->shell = optarg; + break; + default: + errtryhelp(EXIT_FAILURE); + } + } + /* done parsing arguments. check for a username. */ + if (optind < argc) { + if (optind + 1 < argc) { + errx(EXIT_FAILURE, _("cannot handle multiple usernames")); + } + pinfo->username = argv[optind]; + } +} + +/* + * ask_new_shell () -- + * ask the user for a shell and return it. + */ +static char *ask_new_shell(char *question, char *oldshell) +{ + int len; + char *ans = NULL; + size_t dummy = 0; + + if (!oldshell) + oldshell = ""; + printf("%s [%s]:", question, oldshell); + + putchar(' '); + fflush(stdout); + + if (getline(&ans, &dummy, stdin) < 0) + return NULL; + + /* remove the newline at the end of ans. */ + ltrim_whitespace((unsigned char *) ans); + len = rtrim_whitespace((unsigned char *) ans); + if (len == 0) + return NULL; + return ans; +} + +/* + * check_shell () -- if the shell is completely invalid, print + * an error and exit. + */ +static void check_shell(const char *shell) +{ + if (*shell != '/') + errx(EXIT_FAILURE, _("shell must be a full path name")); + if (access(shell, F_OK) < 0) + errx(EXIT_FAILURE, _("\"%s\" does not exist"), shell); + if (access(shell, X_OK) < 0) + errx(EXIT_FAILURE, _("\"%s\" is not executable"), shell); + if (illegal_passwd_chars(shell)) + errx(EXIT_FAILURE, _("%s: has illegal characters"), shell); + if (!is_known_shell(shell)) { +#ifdef ONLY_LISTED_SHELLS + if (!getuid()) + warnx(_("Warning: \"%s\" is not listed in %s."), shell, + _PATH_SHELLS); + else + errx(EXIT_FAILURE, + _("\"%s\" is not listed in %s.\n" + "Use %s -l to see list."), shell, _PATH_SHELLS, + program_invocation_short_name); +#else + warnx(_("\"%s\" is not listed in %s.\n" + "Use %s -l to see list."), shell, _PATH_SHELLS, + program_invocation_short_name); +#endif + } +} + +int main(int argc, char **argv) +{ + char *oldshell, *pwbuf; + int nullshell = 0; + const uid_t uid = getuid(); + struct sinfo info = { NULL }; + struct passwd *pw; + + sanitize_env(); + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + parse_argv(argc, argv, &info); + if (!info.username) { + pw = xgetpwuid(uid, &pwbuf); + if (!pw) + errx(EXIT_FAILURE, _("you (user %d) don't exist."), + uid); + } else { + pw = xgetpwnam(info.username, &pwbuf); + if (!pw) + errx(EXIT_FAILURE, _("user \"%s\" does not exist."), + info.username); + } + +#ifndef HAVE_LIBUSER + if (!(is_local(pw->pw_name))) + errx(EXIT_FAILURE, _("can only change local entries")); +#endif + +#ifdef HAVE_LIBSELINUX + if (is_selinux_enabled() > 0) { + char *user_cxt = NULL; + + if (uid == 0 && !ul_selinux_has_access("passwd", "chsh", &user_cxt)) + errx(EXIT_FAILURE, + _("%s is not authorized to change the shell of %s"), + user_cxt ? : _("Unknown user context"), + pw->pw_name); + + if (ul_setfscreatecon_from_file(_PATH_PASSWD) != 0) + errx(EXIT_FAILURE, + _("can't set default context for %s"), _PATH_PASSWD); + } +#endif + + oldshell = pw->pw_shell; + if (oldshell == NULL || *oldshell == '\0') { + oldshell = _PATH_BSHELL; /* default */ + nullshell = 1; + } + + /* reality check */ +#ifdef HAVE_LIBUSER + /* If we're setuid and not really root, disallow the password change. */ + if (geteuid() != getuid() && uid != pw->pw_uid) { +#else + if (uid != 0 && uid != pw->pw_uid) { +#endif + errno = EACCES; + err(EXIT_FAILURE, + _("running UID doesn't match UID of user we're " + "altering, shell change denied")); + } + if (uid != 0 && !is_known_shell(oldshell)) { + errno = EACCES; + err(EXIT_FAILURE, _("your shell is not in %s, " + "shell change denied"), _PATH_SHELLS); + } + + printf(_("Changing shell for %s.\n"), pw->pw_name); + +#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD) + if (!auth_pam("chsh", uid, pw->pw_name)) { + return EXIT_FAILURE; + } +#endif + if (!info.shell) { + info.shell = ask_new_shell(_("New shell"), oldshell); + if (!info.shell) + return EXIT_SUCCESS; + } + + check_shell(info.shell); + + if (!nullshell && strcmp(oldshell, info.shell) == 0) + errx(EXIT_SUCCESS, _("Shell not changed.")); + +#ifdef HAVE_LIBUSER + if (set_value_libuser("chsh", pw->pw_name, uid, + LU_LOGINSHELL, info.shell) < 0) + errx(EXIT_FAILURE, _("Shell *NOT* changed. Try again later.")); +#else + pw->pw_shell = info.shell; + if (setpwnam(pw, ".chsh") < 0) + err(EXIT_FAILURE, _("setpwnam failed\n" + "Shell *NOT* changed. Try again later.")); +#endif + + printf(_("Shell changed.\n")); + return EXIT_SUCCESS; +} diff --git a/login-utils/islocal.c b/login-utils/islocal.c new file mode 100644 index 0000000..ab5c52e --- /dev/null +++ b/login-utils/islocal.c @@ -0,0 +1,111 @@ +/* + * islocal.c - returns true if user is registered in the local + * /etc/passwd file. Written by Álvaro Martínez Echevarria, + * alvaro@enano.etsit.upm.es, to allow peaceful coexistence with yp. Nov 94. + * + * Hacked a bit by poe@daimi.aau.dk + * See also ftp://ftp.daimi.aau.dk/pub/linux/poe/admutil* + * + * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de, + * to distinguish user names where one is a prefix of the other, + * and to use "pathnames.h". Oct 5, 96. + * + * 1999-02-22 Arkadiusz Miśkiewicz + * - added Native Language Support + * + * 2008-04-06 James Youngman, jay@gnu.org + * - Completely rewritten to remove assumption that /etc/passwd + * lines are < 1024 characters long. Also added unit tests. + */ + +#include +#include +#include + +#include "closestream.h" +#include "islocal.h" +#include "nls.h" +#include "pathnames.h" + +static int is_local_in_file(const char *user, const char *filename) +{ + int local = 0; + size_t match; + int chin, skip; + FILE *f; + + if (NULL == (f = fopen(filename, "r"))) + return -1; + + match = 0u; + skip = 0; + while ((chin = getc(f)) != EOF) { + if (skip) { + /* Looking for the start of the next line. */ + if ('\n' == chin) { + /* Start matching username at the next char. */ + skip = 0; + match = 0u; + } + } else { + if (':' == chin) { + if (0 == user[match]) { + /* Success. */ + local = 1; + /* next line has no test coverage, + * but it is just an optimisation + * anyway. */ + break; + } + /* we read a whole username, but it + * is the wrong user. Skip to the + * next line. */ + skip = 1; + } else if ('\n' == chin) { + /* This line contains no colon; it's + * malformed. No skip since we are already + * at the start of the next line. */ + match = 0u; + } else if (chin != user[match]) { + /* username does not match. */ + skip = 1; + } else { + ++match; + } + } + } + fclose(f); + return local; +} + +int is_local(const char *user) +{ + int rv; + + if ((rv = is_local_in_file(user, _PATH_PASSWD)) < 0) + err(EXIT_FAILURE, _("cannot open %s"), _PATH_PASSWD); + return rv; +} + +#ifdef TEST_PROGRAM +int main(int argc, char *argv[]) +{ + close_stdout_atexit(); + if (argc <= 2) { + fprintf(stderr, _("Usage: %s ...\n"), + argv[0]); + return 1; + } + + int i; + for (i = 2; i < argc; i++) { + const int rv = is_local_in_file(argv[i], argv[1]); + if (rv < 0) { + perror(argv[1]); + return 2; + } + printf("%d:%s\n", rv, argv[i]); + } + return 0; +} +#endif diff --git a/login-utils/islocal.h b/login-utils/islocal.h new file mode 100644 index 0000000..11a1bed --- /dev/null +++ b/login-utils/islocal.h @@ -0,0 +1,6 @@ +#ifndef UTIL_LINUX_LOGIN_ISLOCAL_H +#define UTIL_LINUX_LOGIN_ISLOCAL_H + +extern int is_local(const char *user); + +#endif /* UTIL_LINUX_LOGIN_ISLOCAL_H */ diff --git a/login-utils/last.1 b/login-utils/last.1 new file mode 100644 index 0000000..9ba1bd4 --- /dev/null +++ b/login-utils/last.1 @@ -0,0 +1,239 @@ +'\" t +.\" Title: last +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "LAST" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +last, lastb \- show a listing of last logged in users +.SH "SYNOPSIS" +.sp +\fBlast\fP [options] [\fIusername\fP...] [\fItty\fP...] +.sp +\fBlastb\fP [options] [\fIusername\fP...] [\fItty\fP...] +.SH "DESCRIPTION" +.sp +\fBlast\fP searches back through the \fI/var/log/wtmp\fP file (or the file designated by the \fB\-f\fP option) and displays a list of all users logged in (and out) since that file was created. One or more \fIusernames\fP and/or \fIttys\fP can be given, in which case \fBlast\fP will show only the entries matching those arguments. Names of \fIttys\fP can be abbreviated, thus \fBlast 0\fP is the same as \fBlast tty0\fP. +.sp +When catching a \fBSIGINT\fP signal (generated by the interrupt key, usually control\-C) or a \fBSIGQUIT\fP signal, \fBlast\fP will show how far it has searched through the file; in the case of the \fBSIGINT\fP signal \fBlast\fP will then terminate. +.sp +The pseudo user \fBreboot\fP logs in each time the system is rebooted. Thus \fBlast reboot\fP will show a log of all the reboots since the log file was created. +.sp +\fBlastb\fP is the same as \fBlast\fP, except that by default it shows a log of the \fI/var/log/btmp\fP file, which contains all the bad login attempts. +.SH "OPTIONS" +.sp +\fB\-a\fP, \fB\-\-hostlast\fP +.RS 4 +Display the hostname in the last column. Useful in combination with the \fB\-\-dns\fP option. +.RE +.sp +\fB\-d\fP, \fB\-\-dns\fP +.RS 4 +For non\-local logins, Linux stores not only the host name of the remote host, but its IP number as well. This option translates the IP number back into a hostname. +.RE +.sp +\fB\-f\fP, \fB\-\-file\fP \fIfile\fP +.RS 4 +Tell \fBlast\fP to use a specific \fIfile\fP instead of \fI/var/log/wtmp\fP. The \fB\-\-file\fP option can be given multiple times, and all of the specified files will be processed. +.RE +.sp +\fB\-F\fP, \fB\-\-fulltimes\fP +.RS 4 +Print full login and logout times and dates. +.RE +.sp +\fB\-i\fP, \fB\-\-ip\fP +.RS 4 +Like \fB\-\-dns ,\fP but displays the host\(cqs IP number instead of the name. +.RE +.sp +\fB\-\fP\fInumber\fP; \fB\-n\fP, \fB\-\-limit\fP \fInumber\fP +.RS 4 +Tell \fBlast\fP how many lines to show. +.RE +.sp +\fB\-p\fP, \fB\-\-present\fP \fItime\fP +.RS 4 +Display the users who were present at the specified time. This is like using the options \fB\-\-since\fP and \fB\-\-until\fP together with the same \fItime\fP. +.RE +.sp +\fB\-R\fP, \fB\-\-nohostname\fP +.RS 4 +Suppresses the display of the hostname field. +.RE +.sp +\fB\-s\fP, \fB\-\-since\fP \fItime\fP +.RS 4 +Display the state of logins since the specified \fItime\fP. This is useful, e.g., to easily determine who was logged in at a particular time. The option is often combined with \fB\-\-until\fP. +.RE +.sp +\fB\-t\fP, \fB\-\-until\fP \fItime\fP +.RS 4 +Display the state of logins until the specified \fItime\fP. +.RE +.sp +\fB\-\-time\-format\fP \fIformat\fP +.RS 4 +Define the output timestamp \fIformat\fP to be one of \fInotime\fP, \fIshort\fP, \fIfull\fP, or \fIiso\fP. The \fInotime\fP variant will not print any timestamps at all, \fIshort\fP is the default, and \fIfull\fP is the same as the \fB\-\-fulltimes\fP option. The \fIiso\fP variant will display the timestamp in ISO\-8601 format. The ISO format contains timezone information, making it preferable when printouts are investigated outside of the system. +.RE +.sp +\fB\-w\fP, \fB\-\-fullnames\fP +.RS 4 +Display full user names and domain names in the output. +.RE +.sp +\fB\-x\fP, \fB\-\-system\fP +.RS 4 +Display the system shutdown entries and run level changes. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "TIME FORMATS" +.sp +The options that take the \fItime\fP argument understand the following formats: +.TS +allbox tab(:); +lt lt. +T{ +.sp +YYYYMMDDhhmmss +T}:T{ +.sp + +T} +T{ +.sp +YYYY\-MM\-DD hh:mm:ss +T}:T{ +.sp + +T} +T{ +.sp +YYYY\-MM\-DD hh:mm +T}:T{ +.sp +(seconds will be set to 00) +T} +T{ +.sp +YYYY\-MM\-DD +T}:T{ +.sp +(time will be set to 00:00:00) +T} +T{ +.sp +hh:mm:ss +T}:T{ +.sp +(date will be set to today) +T} +T{ +.sp +hh:mm +T}:T{ +.sp +(date will be set to today, seconds to 00) +T} +T{ +.sp +now +T}:T{ +.sp + +T} +T{ +.sp +yesterday +T}:T{ +.sp +(time is set to 00:00:00) +T} +T{ +.sp +today +T}:T{ +.sp +(time is set to 00:00:00) +T} +T{ +.sp +tomorrow +T}:T{ +.sp +(time is set to 00:00:00) +T} +T{ +.sp ++5min +T}:T{ +.sp + +T} +T{ +.sp +\-5days +T}:T{ +.sp + +T} +.TE +.sp +.SH "FILES" +.sp +\fI/var/log/wtmp\fP, +\fI/var/log/btmp\fP +.SH "NOTES" +.sp +The files \fIwtmp\fP and \fIbtmp\fP might not be found. The system only logs information in these files if they are present. This is a local configuration issue. If you want the files to be used, they can be created with a simple \fBtouch\fP(1) command (for example, \fBtouch /var/log/wtmp\fP). +.sp +An empty entry is a valid type of wtmp entry. It means that an empty file or file with zeros is not interpreted as an error. +.SH "AUTHORS" +.sp +.MTO "miquels\(atcistron.nl" "Miquel van Smoorenburg" "" +.SH "SEE ALSO" +.sp +\fBlogin\fP(1), +\fBwtmp\fP(5), +\fBinit\fP(8), +\fBshutdown\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBlast\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file diff --git a/login-utils/last.1.adoc b/login-utils/last.1.adoc new file mode 100644 index 0000000..1157ae9 --- /dev/null +++ b/login-utils/last.1.adoc @@ -0,0 +1,137 @@ +//po4a: entry man manual +//// +Copyright (C) 1998-2004 Miquel van Smoorenburg. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +//// += last(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: last + +== NAME + +last, lastb - show a listing of last logged in users + +== SYNOPSIS + +*last* [options] [_username_...] [_tty_...] + +*lastb* [options] [_username_...] [_tty_...] + +== DESCRIPTION + +*last* searches back through the _/var/log/wtmp_ file (or the file designated by the *-f* option) and displays a list of all users logged in (and out) since that file was created. One or more _usernames_ and/or _ttys_ can be given, in which case *last* will show only the entries matching those arguments. Names of _ttys_ can be abbreviated, thus *last 0* is the same as *last tty0*. + +When catching a *SIGINT* signal (generated by the interrupt key, usually control-C) or a *SIGQUIT* signal, *last* will show how far it has searched through the file; in the case of the *SIGINT* signal *last* will then terminate. + +The pseudo user *reboot* logs in each time the system is rebooted. Thus *last reboot* will show a log of all the reboots since the log file was created. + +*lastb* is the same as *last*, except that by default it shows a log of the _/var/log/btmp_ file, which contains all the bad login attempts. + +== OPTIONS + +*-a*, *--hostlast*:: +Display the hostname in the last column. Useful in combination with the *--dns* option. + +*-d*, *--dns*:: +For non-local logins, Linux stores not only the host name of the remote host, but its IP number as well. This option translates the IP number back into a hostname. + +*-f*, *--file* _file_:: +Tell *last* to use a specific _file_ instead of _/var/log/wtmp_. The *--file* option can be given multiple times, and all of the specified files will be processed. + +*-F*, *--fulltimes*:: +Print full login and logout times and dates. + +*-i*, *--ip*:: +Like *--dns ,* but displays the host's IP number instead of the name. + +**-**__number__; *-n*, *--limit* _number_:: +Tell *last* how many lines to show. + +*-p*, *--present* _time_:: +Display the users who were present at the specified time. This is like using the options *--since* and *--until* together with the same _time_. + +*-R*, *--nohostname*:: +Suppresses the display of the hostname field. + +*-s*, *--since* _time_:: +Display the state of logins since the specified _time_. This is useful, e.g., to easily determine who was logged in at a particular time. The option is often combined with *--until*. + +*-t*, *--until* _time_:: +Display the state of logins until the specified _time_. + +*--time-format* _format_:: +Define the output timestamp _format_ to be one of _notime_, _short_, _full_, or _iso_. The _notime_ variant will not print any timestamps at all, _short_ is the default, and _full_ is the same as the *--fulltimes* option. The _iso_ variant will display the timestamp in ISO-8601 format. The ISO format contains timezone information, making it preferable when printouts are investigated outside of the system. + +*-w*, *--fullnames*:: +Display full user names and domain names in the output. + +*-x*, *--system*:: +Display the system shutdown entries and run level changes. + +include::man-common/help-version.adoc[] + +== TIME FORMATS + +The options that take the _time_ argument understand the following formats: + +[cols=",",] +|=== +|YYYYMMDDhhmmss | +|YYYY-MM-DD hh:mm:ss | +|YYYY-MM-DD hh:mm |(seconds will be set to 00) +|YYYY-MM-DD |(time will be set to 00:00:00) +|hh:mm:ss |(date will be set to today) +|hh:mm |(date will be set to today, seconds to 00) +|now | +|yesterday |(time is set to 00:00:00) +|today |(time is set to 00:00:00) +|tomorrow |(time is set to 00:00:00) +|+5min | +|-5days | +|=== + +== FILES + +_/var/log/wtmp_, +_/var/log/btmp_ + +== NOTES + +The files _wtmp_ and _btmp_ might not be found. The system only logs information in these files if they are present. This is a local configuration issue. If you want the files to be used, they can be created with a simple *touch*(1) command (for example, *touch /var/log/wtmp*). + +An empty entry is a valid type of wtmp entry. It means that an empty file or file with zeros is not interpreted as an error. + +== AUTHORS + +mailto:miquels@cistron.nl[Miquel van Smoorenburg] + +== SEE ALSO + +*login*(1), +*wtmp*(5), +*init*(8), +*shutdown*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/login-utils/last.c b/login-utils/last.c new file mode 100644 index 0000000..8462927 --- /dev/null +++ b/login-utils/last.c @@ -0,0 +1,1091 @@ +/* + * last(1) from sysvinit project, merged into util-linux in Aug 2013. + * + * Copyright (C) 1991-2004 Miquel van Smoorenburg. + * Copyright (C) 2013 Ondrej Oprala + * Karel Zak + * + * Re-implementation of the 'last' command, this time for Linux. Yes I know + * there is BSD last, but I just felt like writing this. No thanks :-). Also, + * this version gives lots more info (especially with -x) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "nls.h" +#include "optutils.h" +#include "pathnames.h" +#include "xalloc.h" +#include "closestream.h" +#include "carefulputc.h" +#include "strutils.h" +#include "timeutils.h" +#include "monotonic.h" +#include "fileutils.h" + +#ifdef FUZZ_TARGET +#include "fuzz.h" +#endif + +#ifndef SHUTDOWN_TIME +# define SHUTDOWN_TIME 254 +#endif + +#ifndef LAST_LOGIN_LEN +# define LAST_LOGIN_LEN 8 +#endif + +#ifndef LAST_DOMAIN_LEN +# define LAST_DOMAIN_LEN 16 +#endif + +#ifndef LAST_TIMESTAMP_LEN +# define LAST_TIMESTAMP_LEN 32 +#endif + +#define UCHUNKSIZE 16384 /* How much we read at once. */ + +struct last_control { + unsigned int lastb :1, /* Is this command 'lastb' */ + extended :1, /* Lots of info */ + showhost :1, /* Show hostname */ + altlist :1, /* Hostname at the end */ + usedns :1, /* Use DNS to lookup the hostname */ + useip :1; /* Print IP address in number format */ + + unsigned int name_len; /* Number of login name characters to print */ + unsigned int domain_len; /* Number of domain name characters to print */ + unsigned int maxrecs; /* Maximum number of records to list */ + + char **show; /* Match search list */ + + struct timeval boot_time; /* system boot time */ + time_t since; /* at what time to start displaying the file */ + time_t until; /* at what time to stop displaying the file */ + time_t present; /* who where present at time_t */ + unsigned int time_fmt; /* time format */ +}; + +/* Double linked list of struct utmp's */ +struct utmplist { + struct utmpx ut; + struct utmplist *next; + struct utmplist *prev; +}; + +/* Types of listing */ +enum { + R_CRASH = 1, /* No logout record, system boot in between */ + R_DOWN, /* System brought down in decent way */ + R_NORMAL, /* Normal */ + R_NOW, /* Still logged in */ + R_REBOOT, /* Reboot record. */ + R_PHANTOM, /* No logout record but session is stale. */ + R_TIMECHANGE /* NEW_TIME or OLD_TIME */ +}; + +enum { + LAST_TIMEFTM_NONE = 0, + LAST_TIMEFTM_SHORT, + LAST_TIMEFTM_CTIME, + LAST_TIMEFTM_ISO8601, + + LAST_TIMEFTM_HHMM, /* non-public */ +}; + +struct last_timefmt { + const char *name; + int in_len; /* log-in */ + int in_fmt; + int out_len; /* log-out */ + int out_fmt; +}; + +static struct last_timefmt timefmts[] = { + [LAST_TIMEFTM_NONE] = { .name = "notime" }, + [LAST_TIMEFTM_SHORT] = { + .name = "short", + .in_len = 16, + .out_len = 7, + .in_fmt = LAST_TIMEFTM_CTIME, + .out_fmt = LAST_TIMEFTM_HHMM + }, + [LAST_TIMEFTM_CTIME] = { + .name = "full", + .in_len = 24, + .out_len = 26, + .in_fmt = LAST_TIMEFTM_CTIME, + .out_fmt = LAST_TIMEFTM_CTIME + }, + [LAST_TIMEFTM_ISO8601] = { + .name = "iso", + .in_len = 25, + .out_len = 27, + .in_fmt = LAST_TIMEFTM_ISO8601, + .out_fmt = LAST_TIMEFTM_ISO8601 + } +}; + +/* Global variables */ +static unsigned int recsdone; /* Number of records listed */ +static time_t lastdate; /* Last date we've seen */ +static time_t currentdate; /* date when we started processing the file */ + +#ifndef FUZZ_TARGET +/* --time-format=option parser */ +static int which_time_format(const char *s) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(timefmts); i++) { + if (strcmp(timefmts[i].name, s) == 0) + return i; + } + errx(EXIT_FAILURE, _("unknown time format: %s"), s); +} +#endif + +/* + * Read one utmp entry, return in new format. + * Automatically reposition file pointer. + */ +static int uread(FILE *fp, struct utmpx *u, int *quit, const char *filename) +{ + static int utsize; + static char buf[UCHUNKSIZE]; + char tmp[1024]; + static off_t fpos; + static int bpos; + off_t o; + + if (quit == NULL && u != NULL) { + /* + * Normal read. + */ + return fread(u, sizeof(struct utmpx), 1, fp); + } + + if (u == NULL) { + /* + * Initialize and position. + */ + utsize = sizeof(struct utmpx); + fseeko(fp, 0, SEEK_END); + fpos = ftello(fp); + if (fpos == 0) + return 0; + o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE; + if (fseeko(fp, o, SEEK_SET) < 0) { + warn(_("seek on %s failed"), filename); + return 0; + } + bpos = (int)(fpos - o); + if (fread(buf, bpos, 1, fp) != 1) { + warn(_("cannot read %s"), filename); + return 0; + } + fpos = o; + return 1; + } + + /* + * Read one struct. From the buffer if possible. + */ + bpos -= utsize; + if (bpos >= 0) { + memcpy(u, buf + bpos, sizeof(struct utmpx)); + return 1; + } + + /* + * Oops we went "below" the buffer. We should be able to + * seek back UCHUNKSIZE bytes. + */ + fpos -= UCHUNKSIZE; + if (fpos < 0) + return 0; + + /* + * Copy whatever is left in the buffer. + */ + memcpy(tmp + (-bpos), buf, utsize + bpos); + if (fseeko(fp, fpos, SEEK_SET) < 0) { + warn(_("seek on %s failed"), filename); + return 0; + } + + /* + * Read another UCHUNKSIZE bytes. + */ + if (fread(buf, UCHUNKSIZE, 1, fp) != 1) { + warn(_("cannot read %s"), filename); + return 0; + } + + /* + * The end of the UCHUNKSIZE byte buffer should be the first + * few bytes of the current struct utmp. + */ + memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos); + bpos += UCHUNKSIZE; + + memcpy(u, tmp, sizeof(struct utmpx)); + + return 1; +} + +#ifndef FUZZ_TARGET +/* + * Print a short date. + */ +static char *showdate(void) +{ + static char s[CTIME_BUFSIZ]; + + ctime_r(&lastdate, s); + s[16] = 0; + return s; +} + +/* + * SIGINT handler + */ +static void int_handler(int sig __attribute__((unused))) +{ + errx(EXIT_FAILURE, _("Interrupted %s"), showdate()); +} + +/* + * SIGQUIT handler + */ +static void quit_handler(int sig __attribute__((unused))) +{ + warnx(_("Interrupted %s"), showdate()); + signal(SIGQUIT, quit_handler); +} +#endif + +/* + * Lookup a host with DNS. + */ +static int dns_lookup(char *result, int size, int useip, int32_t *a) +{ + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr *sa; + int salen, flags; + int mapped = 0; + + flags = useip ? NI_NUMERICHOST : 0; + + /* + * IPv4 or IPv6 ? + * 1. If last 3 4bytes are 0, must be IPv4 + * 2. If IPv6 in IPv4, handle as IPv4 + * 3. Anything else is IPv6 + * + * Ugly. + */ + if (a[0] == 0 && a[1] == 0 && a[2] == (int32_t)htonl (0xffff)) + mapped = 1; + + if (mapped || (a[1] == 0 && a[2] == 0 && a[3] == 0)) { + /* IPv4 */ + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = mapped ? a[3] : a[0]; + sa = (struct sockaddr *)&sin; + salen = sizeof(sin); + } else { + /* IPv6 */ + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = 0; + memcpy(sin6.sin6_addr.s6_addr, a, 16); + sa = (struct sockaddr *)&sin6; + salen = sizeof(sin6); + } + + return getnameinfo(sa, salen, result, size, NULL, 0, flags); +} + +static int time_formatter(int fmt, char *dst, size_t dlen, time_t *when) +{ + int ret = 0; + + switch (fmt) { + case LAST_TIMEFTM_NONE: + *dst = 0; + break; + case LAST_TIMEFTM_HHMM: + { + struct tm tm; + + localtime_r(when, &tm); + if (!snprintf(dst, dlen, "%02d:%02d", tm.tm_hour, tm.tm_min)) + ret = -1; + break; + } + case LAST_TIMEFTM_CTIME: + { + char buf[CTIME_BUFSIZ]; + + ctime_r(when, buf); + snprintf(dst, dlen, "%s", buf); + ret = rtrim_whitespace((unsigned char *) dst); + break; + } + case LAST_TIMEFTM_ISO8601: + ret = strtime_iso(when, ISO_TIMESTAMP_T, dst, dlen); + break; + default: + abort(); + } + return ret; +} + +/* + * Remove trailing spaces from a string. + */ +static void trim_trailing_spaces(char *s) +{ + char *p; + + for (p = s; *p; ++p) + continue; + while (p > s && isspace(*--p)) + continue; + if (p > s) + ++p; + *p++ = '\n'; + *p = '\0'; +} + +/* + * Show one line of information on screen + */ +static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_time, int what) +{ + time_t secs, utmp_time; + char logintime[LAST_TIMESTAMP_LEN]; + char logouttime[LAST_TIMESTAMP_LEN]; + char length[LAST_TIMESTAMP_LEN]; + char final[512]; + char utline[sizeof(p->ut_line) + 1]; + char domain[256]; + char *s; + int mins, hours, days; + int r, len; + struct last_timefmt *fmt; + + /* + * uucp and ftp have special-type entries + */ + mem2strcpy(utline, p->ut_line, sizeof(p->ut_line), sizeof(utline)); + if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3])) + utline[3] = 0; + if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4])) + utline[4] = 0; + + /* + * Is this something we want to show? + */ + if (ctl->show) { + char **walk; + for (walk = ctl->show; *walk; walk++) { + if (strncmp(p->ut_user, *walk, sizeof(p->ut_user)) == 0 || + strcmp(utline, *walk) == 0 || + (strncmp(utline, "tty", 3) == 0 && + strcmp(utline + 3, *walk) == 0)) break; + } + if (*walk == NULL) return 0; + } + + /* + * Calculate times + */ + fmt = &timefmts[ctl->time_fmt]; + + utmp_time = p->ut_tv.tv_sec; + + if (ctl->present) { + if (ctl->present < utmp_time) + return 0; + if (0 < logout_time && logout_time < ctl->present) + return 0; + } + + /* log-in time */ + if (time_formatter(fmt->in_fmt, logintime, + sizeof(logintime), &utmp_time) < 0) + errx(EXIT_FAILURE, _("preallocation size exceeded")); + + /* log-out time */ + secs = logout_time - utmp_time; /* Under strange circumstances, secs < 0 can happen */ + mins = (secs / 60) % 60; + hours = (secs / 3600) % 24; + days = secs / 86400; + + strcpy(logouttime, "- "); + if (time_formatter(fmt->out_fmt, logouttime + 2, + sizeof(logouttime) - 2, &logout_time) < 0) + errx(EXIT_FAILURE, _("preallocation size exceeded")); + + if (logout_time == currentdate) { + if (ctl->time_fmt > LAST_TIMEFTM_SHORT) { + snprintf(logouttime, sizeof(logouttime), " still running"); + length[0] = 0; + } else { + snprintf(logouttime, sizeof(logouttime), " still"); + snprintf(length, sizeof(length), "running"); + } + } else if (days) { + snprintf(length, sizeof(length), "(%d+%02d:%02d)", days, abs(hours), abs(mins)); /* hours and mins always shown as positive (w/o minus sign!) even if secs < 0 */ + } else if (hours) { + snprintf(length, sizeof(length), " (%02d:%02d)", hours, abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */ + } else if (secs >= 0) { + snprintf(length, sizeof(length), " (%02d:%02d)", hours, mins); + } else { + snprintf(length, sizeof(length), " (-00:%02d)", abs(mins)); /* mins always shown as positive (w/o minus sign!) even if secs < 0 */ + } + + switch(what) { + case R_CRASH: + snprintf(logouttime, sizeof(logouttime), "- crash"); + break; + case R_DOWN: + snprintf(logouttime, sizeof(logouttime), "- down "); + break; + case R_NOW: + if (ctl->time_fmt > LAST_TIMEFTM_SHORT) { + snprintf(logouttime, sizeof(logouttime), " still logged in"); + length[0] = 0; + } else { + snprintf(logouttime, sizeof(logouttime), " still"); + snprintf(length, sizeof(length), "logged in"); + } + break; + case R_PHANTOM: + if (ctl->time_fmt > LAST_TIMEFTM_SHORT) { + snprintf(logouttime, sizeof(logouttime), " gone - no logout"); + length[0] = 0; + } else if (ctl->time_fmt == LAST_TIMEFTM_SHORT) { + snprintf(logouttime, sizeof(logouttime), " gone"); + snprintf(length, sizeof(length), "- no logout"); + } else { + logouttime[0] = 0; + snprintf(length, sizeof(length), "no logout"); + } + break; + case R_TIMECHANGE: + logouttime[0] = 0; + length[0] = 0; + break; + case R_NORMAL: + case R_REBOOT: + break; + default: + abort(); + } + + /* + * Look up host with DNS if needed. + */ + r = -1; + if (ctl->usedns || ctl->useip) + r = dns_lookup(domain, sizeof(domain), ctl->useip, (int32_t*)p->ut_addr_v6); + if (r < 0) + mem2strcpy(domain, p->ut_host, sizeof(p->ut_host), sizeof(domain)); + + if (ctl->showhost) { + if (!ctl->altlist) { + len = snprintf(final, sizeof(final), + "%-8.*s %-12.12s %-16.*s %-*.*s %-*.*s %s\n", + ctl->name_len, p->ut_user, utline, + ctl->domain_len, domain, + fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len, + logouttime, length); + } else { + len = snprintf(final, sizeof(final), + "%-8.*s %-12.12s %-*.*s %-*.*s %-12.12s %s\n", + ctl->name_len, p->ut_user, utline, + fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len, + logouttime, length, domain); + } + } else + len = snprintf(final, sizeof(final), + "%-8.*s %-12.12s %-*.*s %-*.*s %s\n", + ctl->name_len, p->ut_user, utline, + fmt->in_len, fmt->in_len, logintime, fmt->out_len, fmt->out_len, + logouttime, length); + +#if defined(__GLIBC__) +# if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0) + final[sizeof(final)-1] = '\0'; +# endif +#endif + + trim_trailing_spaces(final); + /* + * Print out "final" string safely. + */ + for (s = final; *s; s++) + fputc_careful(*s, stdout, '*'); + + if (len < 0 || (size_t)len >= sizeof(final)) + putchar('\n'); + + recsdone++; + if (ctl->maxrecs && ctl->maxrecs <= recsdone) + return 1; + + return 0; +} + +#ifndef FUZZ_TARGET +static void __attribute__((__noreturn__)) usage(const struct last_control *ctl) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _( + " %s [options] [...] [...]\n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Show a listing of last logged in users.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" - how many lines to show\n"), out); + fputs(_(" -a, --hostlast display hostnames in the last column\n"), out); + fputs(_(" -d, --dns translate the IP number back into a hostname\n"), out); + fprintf(out, + _(" -f, --file use a specific file instead of %s\n"), ctl->lastb ? _PATH_BTMP : _PATH_WTMP); + fputs(_(" -F, --fulltimes print full login and logout times and dates\n"), out); + fputs(_(" -i, --ip display IP numbers in numbers-and-dots notation\n"), out); + fputs(_(" -n, --limit how many lines to show\n"), out); + fputs(_(" -R, --nohostname don't display the hostname field\n"), out); + fputs(_(" -s, --since