From ab1cfad5b72e33ef3a6c3dd73da8dd9e4df304fb Mon Sep 17 00:00:00 2001 From: Ondrej Oprala Date: Fri, 4 Apr 2014 17:58:06 +0200 Subject: [PATCH] lslogins(1): skeleton and argparsing for a new utility Signed-off-by: Ondrej Oprala --- configure.ac | 5 + sys-utils/Makemodule.am | 10 ++ sys-utils/lslogins.1 | 118 +++++++++++++ sys-utils/lslogins.c | 383 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 sys-utils/lslogins.1 create mode 100644 sys-utils/lslogins.c diff --git a/configure.ac b/configure.ac index c5a3ef5a3..b83df0d06 100644 --- a/configure.ac +++ b/configure.ac @@ -1047,6 +1047,11 @@ UL_REQUIRES_HAVE([lscpu], [cpu_set_t], [cpu_set_t type]) AM_CONDITIONAL([BUILD_LSCPU], [test "x$build_lscpu" = xyes]) +UL_BUILD_INIT([lslogins], [check]) +UL_REQUIRES_BUILD([lslogins], [libsmartcols]) +AM_CONDITIONAL([BUILD_LSLOGINS], [test "x$build_lslogins" = xyes]) + + UL_BUILD_INIT([chcpu], [check]) UL_REQUIRES_LINUX([chcpu]) UL_REQUIRES_HAVE([chcpu], [cpu_set_t], [cpu_set_t type]) diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index 9d69bc60e..2723768f9 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -334,3 +334,13 @@ dist_man_MANS += sys-utils/setpriv.1 setpriv_SOURCES = sys-utils/setpriv.c setpriv_LDADD = $(LDADD) -lcap-ng libcommon.la endif + +if BUILD_LSLOGINS +usrbin_exec_PROGRAMS += lslogins +dist_man_MANS += sys-utils/lslogins.1 +lslogins_SOURCES = \ + sys-utils/lslogins.c \ + sys-utils/lslogins.h +lslogins_LDADD = $(LDADD) libcommon.la libsmartcols.la +lslogins_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +endif diff --git a/sys-utils/lslogins.1 b/sys-utils/lslogins.1 new file mode 100644 index 000000000..9b62cb86c --- /dev/null +++ b/sys-utils/lslogins.1 @@ -0,0 +1,118 @@ +.\" Copyright 2014 Ondrej Oprala (ondrej.oprala@gmail.com) +.\" May be distributed under the GNU General Public License +.TH LSLOGINS "1" "April 2014" "util-linux" "User Commands" +.SH NAME +lslogins \- display information about known users in the system +.SH SYNOPSIS +.B lslogins +[\fI-adehmoptvx\fR] [-s|-u[\fI=UID\fR]] [-g \fIGROUPS\fR] [-l \fILOGINS\fR] +.SH DESCRIPTION +.PP +Examine the wtmp and btmp logs, /etc/shadow (if necessary) and /etc/passwd +and output the desired data. +.PP +Mandatory arguments to long options are mandatory for short options too. +.PP +The default action is to list info about all the users in the system. +.SH OPTIONS +.PP +Display info about existing users. +.TP +\fB\-a\fR, \fB\-\-acc\-expiration\fR +Display data about the date of last password change and the account expiration date (see shadow(5) for more info). +.TP +\fB\-c\fR, \fB\-\-colon\fR +Separate info about each user with a colon instead of a newline. +.TP +\fB\-d\fR, \fB\-\-duplicates\fR +Show users with duplicate UIDs. +.TP +\fB\-e\fR, \fB\-\-export\fR +Output data in the format of NAME=VALUE. +.TP +\fB\-f\fR, \fB\-\-failed\fR +Display data about the users' last failed login attempts. +.TP +\fB\-g\fR, \fB\-\-groups\fR=\fIGROUPS\fR +Only show data of users belonging to \fIGROUPS\fR. More than one group may be specified; the list has to be comma-separated. +.TP +\fB\-\-last\fR +Display data containing information about the users' last login sessions. +.TP +\fB\-l\fR, \fB\-\-logins\fR=\fILOGINS\fR +Only show data of users with a login specified in \fILOGINS\fR. More than one login may be specified; the list has to be comma-separated. +.TP +\fB\-m\fR, \fB\-\-more\fR +Show secondary groups as well. +.TP +\fB\-n\fR, \fB\-\-newline\fR +Display each piece of information on a separate line. +.TP +\fB\-p\fR, \fB\-\-no\-password\fR +Show users without a password. +.TP +\fB\-r\fR, \fB\-\-raw\fR +Raw output (no columnation). +.TP +\fB\-s\fR, \fB\-\-sys\-accs\fR[=\fIUID\fR] +Show system accounts. These are by the default all accounts with UID below 1000 (non-inclusive), with the exception of either nobody or nfsnobody (UID 65534). The UID +treshold can also be specified explicitly (necessary for some distributions that allocate UIDs +starting from 100, 500 - or an entirely different value - rather than 1000). +.TP +\fB\-t\fR, \fB\-\-sort\fR +Sort by user name, rather than UID. +.TP +\fB\-u\fR, \fB\-\-user\-accs\fR[=\fIUID\fR] +Show user accounts. These are by the default all accounts with UID above 1000 (inclusive), with the exception of either nobody or nfsnobody (UID 65534). The UID +treshold can also be specified explicitly (necessary for some distributions that allocate UIDs +starting from 100, 500 - or an entirely different value - rather than 1000). +.TP +\fB\-x\fR, \fB\-\-extra\fR +Show extra information about users - home diretory, default login shell, password age and expiry information. +.TP +\fB\-z\fR, \fB\-\-print0\fR +Delimit user entries with a nul character, instead of a newline. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help information and exit. +\fB\-v\fR, \fB\-\-version\fR +Display version information and exit. + + + +Note that switches -a and -x require root priviliges. Otherwise their fields will state for each entry. +.sp +.SH COLORS +Implicit coloring can be disabled as follows: +.RS + +.br +.BI "touch /etc/terminal-colors.d/lslogins.disable" +.br + +.RE +For more details see +.BR terminal-colors.d (5). +.SH EXIT STATUS +.TP +0 +if OK, +.TP +1 +if incorrect arguments specified, +.TP +2 +if a serious error occurs (e.g. a corrupt log). +.SH SEE ALSO +\fBgroup\fP(5), \fBpasswd\fP(5), \fBshadow\fP(5), \fButmp\fP(5) +.SH HISTORY +The \fBlslogins\fP utility is inspired by the \fBlogins\fP utility, which first appeared in FreeBSD 4.10. +.SH AUTHORS +.MT ondrej.oprala@gmail.com +Ondrej Oprala +.ME +.SH AVAILABILITY +The lslogins command is part of the util-linux package and is available from +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/sys-utils/lslogins.c b/sys-utils/lslogins.c new file mode 100644 index 000000000..c9f812f36 --- /dev/null +++ b/sys-utils/lslogins.c @@ -0,0 +1,383 @@ +/* + * lslogins - List information about users on the system + * + * Copyright (C) 2014 Ondrej Oprala + * + * 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 would 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 "c.h" +#include "nls.h" +#include "closestream.h" +#include "xalloc.h" +#include "strutils.h" +#include "optutils.h" + +/* + * column description + */ +struct lslogins_coldesc { + const char *name; + const char *help; + + unsigned int is_abbr:1; /* name is abbreviation */ +}; + +/* the most uber of flags */ +static int uberflag; + +/* we use the value of outmode to determine + * appropriate flags for the libsmartcols table + * (e.g., a value of out_newline would imply a raw + * table with the column separator set to '\n'). + */ +static int outmode; +/* + * output modes + */ +enum { + out_colon = 0, + out_export, + out_newline, + out_raw, + out_nul, +}; + +struct lslogins_user { + char *login; + uid_t uid; + char *group; + gid_t gid; + char *gecos; + + int nopasswd:1; + + char *sgroups; + + struct tm *pwd_ctime; + struct tm *pwd_expir; + + struct tm *last_login; + char * last_tty; + char * last_hostname; + + struct tm *failed_login; + struct tm *failed_tty; + + char *homedir; + char *shell; + char *pwd_status; + char *hush_status; +}; +/* + * flags + */ +enum { + F_EXPIR = (1 << 0), + F_DUP = (1 << 1), + F_EXPRT = (1 << 2), + F_MORE = (1 << 3), + F_NOPWD = (1 << 4), + F_SYSAC = (1 << 5), + F_USRAC = (1 << 6), + F_SORT = (1 << 7), + F_EXTRA = (1 << 8), + F_FAIL = (1 << 9), + F_LAST = (1 << 10), +}; + +/* + * IDs + */ +enum { + COL_LOGIN = 0, + COL_UID, + COL_NOPASSWD, + COL_PGRP, + COL_PGID, + COL_SGRPS, + COL_HOME, + COL_SHELL, + COL_FULLNAME, + COL_LAST_LOGIN, + COL_LAST_TTY, + COL_LAST_HOSTNAME, + COL_FAILED_LOGIN, + COL_FAILED_TTY, + COL_HUSH_STATUS, + COL_PWD_STATUS, + COL_PWD_EXPIR, + COL_PWD_CTIME, + /*COL_PWD_CTIME_MAX, + COL_PWD_CTIME_MIN,*/ +}; + +static struct lslogins_coldesc coldescs[] = +{ + [COL_LOGIN] = { "LOGIN", N_("user/system login"), 1 }, + [COL_UID] = { "UID", N_("user UID") }, + [COL_NOPASSWD] = { "HAS PASSWORD", N_("account has a password?") }, + [COL_PGRP] = { "GRP", N_("primary group name") }, + [COL_PGID] = { "GRP_GID", N_("primary group GID") }, + [COL_SGRPS] = { "SEC_GRPS", N_("secondary group names and GIDs") }, + [COL_HOME] = { "HOMEDIR", N_("home directory") }, + [COL_SHELL] = { "SHELL", N_("login shell") }, + [COL_FULLNAME] = { "FULLNAME", N_("full user name") }, + [COL_LAST_LOGIN] = { "LAST_LOGIN", N_("date of last login") }, + [COL_LAST_TTY] = { "LAST_TTY", N_("last tty used") }, + [COL_LAST_HOSTNAME] = { "LAST_HOSTNAME", N_("hostname during the last session") }, + [COL_FAILED_LOGIN] = { "FAILED_LOGIN", N_("date of last failed login") }, + [COL_FAILED_TTY] = { "FAILED_TTY", N_("where did the login fail?") }, + [COL_HUSH_STATUS] = { "HUSH_STATUS", N_("User's hush settings") }, + [COL_PWD_STATUS] = { "PWD_STATUS", N_("password status - see the -x option description") }, + [COL_PWD_EXPIR] = { "PWD_EXPIR", N_("password expiration date") }, + [COL_PWD_CTIME] = { "PWD_CHANGE", N_("date of last password change") }, + /*[COL_PWD_CTIME_MAX] = { "PWD UNTIL", N_("max number of days a password may remain unchanged") }, + [COL_PWD_CTIME_MIN] = { "PWD CAN CHANGE", N_("number of days required between changes") },*/ +}; + +static int +column_name_to_id(const char *name, size_t namesz) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(coldescs); i++) { + const char *cn = coldescs[i].name; + + if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) + return i; + } + warnx(_("unknown column: %s"), name); + return -1; +} + +static void __attribute__((__noreturn__)) usage(FILE *out) +{ + size_t i; + + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options]\n"), program_invocation_short_name); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -a, --acc-expiration Display data\n"), out); + fputs(_(" -c, --colon-separate Display data in a format similar to /etc/passwd\n"), out); + fputs(_(" -d, --duplicates Display users with duplicate UIDs\n"), out); + fputs(_(" -e, --export Display in an export-able output format\n"), out); + fputs(_(" -f, --failed Display data about the last users' failed logins\n"), out); + fputs(_(" -g, --groups= Display users belonging to a group in GROUPS\n"), out); + fputs(_(" -l, --logins= Display only users from LOGINS\n"), out); + fputs(_(" --last Show info about the last login sessions\n"), out); + fputs(_(" -m, --more Display secondary groups as well\n"), out); + fputs(_(" -n, --newline Display each piece of information on a new line\n"), out); + fputs(_(" -o, --output[=] Define the columns to output\n"), out); + fputs(_(" -p, --no-password Display users without a password\n"), out); + fputs(_(" -r, --raw Display the raw table\n"), out); + fputs(_(" -s, --sys-accs[=] Display system accounts\n"), out); + fputs(_(" -t, --sort Sort output by login instead of UID\n"), out); + fputs(_(" -u, --user-accs[=] Display user accounts\n"), out); + fputs(_(" -x, --extra Display extra information\n"), out); + fputs(_(" -z, --print0 Delimit user entries with a nul character"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + + fprintf(out, _("\nAvailable columns:\n")); + + for (i = 0; i < ARRAY_SIZE(coldescs); i++) + fprintf(out, " %14s %s\n", coldescs[i].name, _(coldescs[i].help)); + + fprintf(out, _("\nFor more details see lslogins(1).\n")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + int c; + int columns[ARRAY_SIZE(coldescs)], ncolumns = 0; + char *logins = NULL, *groups = NULL; + + /* long only options. */ + enum { + OPT_LAST = CHAR_MAX + 1, + OPT_VER, + }; + static const struct option longopts[] = { + { "acc-expiration", no_argument, 0, 'a' }, + { "colon", no_argument, 0, 'c' }, + { "duplicates", no_argument, 0, 'd' }, + { "export", no_argument, 0, 'e' }, + { "failed", no_argument, 0, 'f' }, + { "groups", required_argument, 0, 'g' }, + { "help", no_argument, 0, 'h' }, + { "logins", required_argument, 0, 'l' }, + { "more", no_argument, 0, 'm' }, + { "newline", no_argument, 0, 'n' }, + { "output", optional_argument, 0, 'o' }, + { "no-password", no_argument, 0, 'p' }, + { "last", no_argument, 0, OPT_LAST }, + { "raw", no_argument, 0, 'r' }, + { "sys-accs", optional_argument, 0, 's' }, + { "sort", no_argument, 0, 't' }, + { "user-accs", optional_argument, 0, 'u' }, + { "version", no_argument, 0, OPT_VER }, + { "extra", no_argument, 0, 'x' }, + { "print0", no_argument, 0, 'z' }, + { NULL, 0, 0, 0 } + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'c','e','n','r','z' }, + { 0 } + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + while ((c = getopt_long(argc, argv, "acdefg:hl:mno::prs::tu::xz", + longopts, NULL)) != -1) { + + err_exclusive_options(c, longopts, excl, excl_st); + + switch (c) { + case 'a': + uberflag |= F_EXPIR; + break; + case 'c': + outmode = out_colon; + break; + case 'd': + uberflag |= F_DUP; + break; + case 'e': + outmode = out_export; + break; + case 'f': + uberflag |= F_FAIL; + break; + case 'g': + groups = strdup(optarg); + if (!groups) + return EXIT_FAILURE; + break; + case 'h': + usage(stdout); + case 'l': + logins = strdup(optarg); + if (!logins) + return EXIT_FAILURE; + break; + case 'm': + uberflag |= F_MORE; + break; + case 'n': + outmode = out_newline; + break; + case 'o': + if (optarg) { + if (*optarg == '=') + optarg++; + ncolumns = string_to_idarray(optarg, + columns, ARRAY_SIZE(columns), + column_name_to_id); + if (ncolumns < 0) + return EXIT_FAILURE; + } + break; + case 'p': + uberflag |= F_NOPWD; + break; + case 'r': + outmode = out_raw; + break; + case OPT_LAST: + uberflag |= F_LAST; + break; + case 's': + uberflag |= F_SYSAC; + break; + case 't': + uberflag |= F_SORT; + break; + case 'u': + uberflag |= F_USRAC; + break; + case OPT_VER: + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + return EXIT_SUCCESS; + case 'x': + uberflag |= F_EXTRA; + break; + case 'z': + outmode = out_nul; + break; + default: + usage(stderr); + } + } + if (argc != optind) + usage(stderr); + + if (!ncolumns) { + columns[ncolumns++] = COL_LOGIN; + columns[ncolumns++] = COL_UID; + columns[ncolumns++] = COL_PGRP; + columns[ncolumns++] = COL_PGID; + columns[ncolumns++] = COL_FULLNAME; + + if (uberflag & F_NOPWD) { + columns[ncolumns++] = COL_NOPASSWD; + } + if (uberflag & F_MORE) { + columns[ncolumns++] = COL_SGRPS; + } + if (uberflag & F_EXPIR) { + columns[ncolumns++] = COL_PWD_CTIME; + columns[ncolumns++] = COL_PWD_EXPIR; + } + if (uberflag & F_LAST) { + columns[ncolumns++] = COL_LAST_LOGIN; + columns[ncolumns++] = COL_LAST_TTY; + columns[ncolumns++] = COL_LAST_HOSTNAME; + } + if (uberflag & F_FAIL) { + columns[ncolumns++] = COL_FAILED_LOGIN; + columns[ncolumns++] = COL_FAILED_TTY; + } + if (uberflag & F_EXTRA) { + columns[ncolumns++] = COL_HOME; + columns[ncolumns++] = COL_SHELL; + columns[ncolumns++] = COL_PWD_STATUS; + columns[ncolumns++] = COL_HUSH_STATUS; + /* columns[ncolumns++] = COL_PWD_CTIME_MAX; + columns[ncolumns++] = COL_PWD_CTIME_MIN; */ + } + } + return EXIT_SUCCESS; +}