Merge remote-tracking branch 'sami/2014wk49'

* sami/2014wk49:
  chfn: make command to obey login.defs CHFN_RESTRICT instructions
  chfn: remove set_changed_data() and add add_missing()
  chfn: rename prompt() to ask_new_field()
  chfn: move new and old finger structs to chfn control struct
  chfn: clean up parse_argv()
  chfn: add minimalistic struct chfn_control
  chfn: simplify parse_passwd() by using strsep()
  chfn: fix usage() regression
  chfn: use xasprintf() rather than bunch of strlen() and malloc() calls
  chfn: rewrite prompt() to use strutils
  chfn: remove function prototypes
  lslogins: use hardcoded paths from pathnames.h
  lslogins: add space to systemd journal header and message
  lslogins: reject unknown time format arguments
  lslogins: fix short options
  lslogins: tell why command failed
  lslogins: make journald last logs time stamps to honor --time-format
  lslogins: allow changing password changed and expiration time formats
This commit is contained in:
Karel Zak 2014-12-19 14:28:42 +01:00
commit 47123602e6
4 changed files with 382 additions and 380 deletions

View File

@ -38,6 +38,7 @@
#endif
#define _PATH_MOTDFILE "/etc/motd"
#define _PATH_NOLOGIN "/etc/nologin"
#define _PATH_VAR_NOLOGIN "/var/run/nologin"
#define _PATH_LOGIN "/bin/login"
#define _PATH_INITTAB "/etc/inittab"

View File

@ -119,7 +119,11 @@ chfn_chsh_sources += \
chfn_chsh_ldadd += -lselinux
endif
chfn_SOURCES = login-utils/chfn.c $(chfn_chsh_sources)
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)

View File

@ -40,6 +40,7 @@
#include "setpwnam.h"
#include "strutils.h"
#include "xalloc.h"
#include "logindefs.h"
#ifdef HAVE_LIBSELINUX
# include <selinux/selinux.h>
@ -54,11 +55,7 @@
# include "auth.h"
#endif
static char buf[1024];
struct finfo {
struct passwd *pw;
char *username;
char *full_name;
char *office;
char *office_phone;
@ -66,13 +63,22 @@ struct finfo {
char *other;
};
static int parse_argv(int argc, char *argv[], struct finfo *pinfo);
static void parse_passwd(struct passwd *pw, struct finfo *pinfo);
static void ask_info(struct finfo *oldfp, struct finfo *newfp);
static char *prompt(char *question, char *def_val);
static int check_gecos_string(char *msg, char *gecos);
static int set_changed_data(struct finfo *oldfp, struct finfo *newfp);
static int save_new_data(struct finfo *pinfo);
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
@ -87,61 +93,340 @@ static void __attribute__((__noreturn__)) usage(FILE *fp)
fputs(_(" -p, --office-phone <phone> office phone number\n"), fp);
fputs(_(" -h, --home-phone <phone> home phone number\n"), fp);
fputs(USAGE_SEPARATOR, fp);
fputs(USAGE_HELP, fp);
fputs(USAGE_VERSION, fp);
fputs(_(" -u, --help display this help and exit\n"), fp);
fputs(_(" -v, --version output version information and exit\n"), fp);
fprintf(fp, USAGE_MAN_TAIL("chfn(1)"));
exit(fp == stderr ? EXIT_FAILURE : 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)
{
unsigned int i, c;
const size_t len = strlen(gecos);
if (MAX_FIELD_SIZE < len) {
warnx(_("field %s is too long"), msg);
return -1;
}
for (i = 0; i < len; i++) {
c = gecos[i];
if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
warnx(_("%s: '%c' is not allowed"), msg, c);
return -1;
}
if (iscntrl(c)) {
warnx(_("%s: control characters are not allowed"), msg);
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, 0, 'f'},
{"office", required_argument, 0, 'o'},
{"office-phone", required_argument, 0, 'p'},
{"home-phone", required_argument, 0, 'h'},
{"help", no_argument, 0, 'u'},
{"version", no_argument, 0, 'v'},
{NULL, no_argument, 0, '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':
printf(UTIL_LINUX_VERSION);
exit(EXIT_SUCCESS);
case 'u':
usage(stdout);
default:
usage(stderr);
}
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)
usage(stderr);
ctl->username = argv[optind];
}
return;
}
/*
* 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 *ans;
char buf[MAX_FIELD_SIZE + 2];
if (!def_val)
def_val = "";
while (true) {
printf("%s [%s]: ", question, def_val);
__fpurge(stdin);
if (fgets(buf, sizeof(buf), stdin) == NULL)
errx(EXIT_FAILURE, _("Aborted."));
ans = buf;
/* remove white spaces from string end */
ltrim_whitespace((unsigned char *) ans);
len = rtrim_whitespace((unsigned char *) ans);
if (len == 0)
return xstrdup(def_val);
if (!strcasecmp(ans, "none")) {
ctl->changed = 1;
return xstrdup("");
}
if (check_gecos_string(question, ans) >= 0)
break;
}
ctl->changed = 1;
return xstrdup(ans);
}
/*
* 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);
return;
}
/*
* 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) {
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) < 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 finfo oldf, newf;
int interactive;
struct chfn_control ctl = {
.interactive = 1
};
sanitize_env();
setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
atexit(close_stdout);
/*
* "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".
*
* the reason the new finger information is not put _immediately_
* into "oldf" is that on the command line, new finger information
* can be specified before we know what user the information is
* being specified for.
*/
uid = getuid();
memset(&oldf, 0, sizeof(oldf));
memset(&newf, 0, sizeof(newf));
interactive = parse_argv(argc, argv, &newf);
if (!newf.username) {
parse_passwd(getpwuid(uid), &oldf);
if (!oldf.username)
/* 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 {
parse_passwd(getpwnam(newf.username), &oldf);
if (!oldf.username)
ctl.pw = getpwnam(ctl.username);
if (!ctl.pw)
errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
newf.username);
ctl.username);
}
parse_passwd(&ctl);
#ifndef HAVE_LIBUSER
if (!(is_local(oldf.username)))
if (!(is_local(ctl.username)))
errx(EXIT_FAILURE, _("can only change local entries"));
#endif
#ifdef HAVE_LIBSELINUX
if (is_selinux_enabled() > 0) {
if (uid == 0) {
if (checkAccess(oldf.username, PASSWD__CHFN) != 0) {
if (checkAccess(ctl.username, PASSWD__CHFN) != 0) {
security_context_t user_context;
if (getprevcon(&user_context) < 0)
user_context = NULL;
@ -149,7 +434,7 @@ int main(int argc, char **argv)
_("%s is not authorized to change "
"the finger info of %s"),
user_context ? : _("Unknown user context"),
oldf.username);
ctl.username);
}
}
if (setupDefaultContext(_PATH_PASSWD))
@ -160,319 +445,32 @@ int main(int argc, char **argv)
#ifdef HAVE_LIBUSER
/* If we're setuid and not really root, disallow the password change. */
if (geteuid() != getuid() && uid != oldf.pw->pw_uid) {
if (geteuid() != getuid() && uid != ctl.pw->pw_uid) {
#else
if (uid != 0 && uid != oldf.pw->pw_uid) {
if (uid != 0 && uid != ctl.oldf.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"), oldf.username);
printf(_("Changing finger information for %s.\n"), ctl.username);
#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
if(!auth_pam("chfn", uid, oldf.username)) {
if (!auth_pam("chfn", uid, ctl.username)) {
return EXIT_FAILURE;
}
#endif
if (interactive)
ask_info(&oldf, &newf);
if (ctl.interactive)
ask_info(&ctl);
if (!set_changed_data(&oldf, &newf)) {
add_missing(&ctl);
if (!ctl.changed) {
printf(_("Finger information not changed.\n"));
return EXIT_SUCCESS;
}
return save_new_data(&oldf) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
/*
* parse_argv () --
* parse the command line arguments.
* returns true if no information beyond the username was given.
*/
static int parse_argv(int argc, char *argv[], struct finfo *pinfo)
{
int index, c, status;
int info_given;
static struct option long_options[] = {
{"full-name", required_argument, 0, 'f'},
{"office", required_argument, 0, 'o'},
{"office-phone", required_argument, 0, 'p'},
{"home-phone", required_argument, 0, 'h'},
{"help", no_argument, 0, 'u'},
{"version", no_argument, 0, 'v'},
{NULL, no_argument, 0, '0'},
};
optind = 0;
info_given = false;
while (true) {
c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
&index);
if (c == -1)
break;
/* version? output version and exit. */
if (c == 'v') {
printf(UTIL_LINUX_VERSION);
exit(EXIT_SUCCESS);
}
if (c == 'u')
usage(stdout);
/* all other options must have an argument. */
if (!optarg)
usage(stderr);
/* ok, we were given an argument */
info_given = true;
/* now store the argument */
switch (c) {
case 'f':
pinfo->full_name = optarg;
status = check_gecos_string(_("Name"), optarg);
break;
case 'o':
pinfo->office = optarg;
status = check_gecos_string(_("Office"), optarg);
break;
case 'p':
pinfo->office_phone = optarg;
status = check_gecos_string(_("Office Phone"), optarg);
break;
case 'h':
pinfo->home_phone = optarg;
status = check_gecos_string(_("Home Phone"), optarg);
break;
default:
usage(stderr);
}
if (status != 0)
exit(EXIT_FAILURE);
}
/* done parsing arguments. check for a username. */
if (optind < argc) {
if (optind + 1 < argc)
usage(stderr);
pinfo->username = argv[optind];
}
return !info_given;
}
/*
* parse_passwd () --
* take a struct password and fill in the fields of the struct finfo.
*/
static void parse_passwd(struct passwd *pw, struct finfo *pinfo)
{
char *gecos;
char *cp;
if (pw) {
pinfo->pw = pw;
pinfo->username = pw->pw_name;
/* use pw_gecos - we take a copy since PAM destroys the original */
gecos = xstrdup(pw->pw_gecos);
cp = (gecos ? gecos : "");
pinfo->full_name = cp;
cp = strchr(cp, ',');
if (cp) {
*cp = 0, cp++;
} else
return;
pinfo->office = cp;
cp = strchr(cp, ',');
if (cp) {
*cp = 0, cp++;
} else
return;
pinfo->office_phone = cp;
cp = strchr(cp, ',');
if (cp) {
*cp = 0, cp++;
} else
return;
pinfo->home_phone = cp;
/* extra fields contain site-specific information, and can
* not be changed by this version of chfn. */
cp = strchr(cp, ',');
if (cp) {
*cp = 0, cp++;
} else
return;
pinfo->other = cp;
}
}
/*
* ask_info () --
* prompt the user for the finger information and store it.
*/
static void ask_info(struct finfo *oldfp, struct finfo *newfp)
{
newfp->full_name = prompt(_("Name"), oldfp->full_name);
newfp->office = prompt(_("Office"), oldfp->office);
newfp->office_phone = prompt(_("Office Phone"), oldfp->office_phone);
newfp->home_phone = prompt(_("Home Phone"), oldfp->home_phone);
printf("\n");
}
/*
* prompt () --
* ask the user for a given field and check that the string is legal.
*/
static char *prompt(char *question, char *def_val)
{
static char *blank = "none";
int len;
char *ans, *cp;
while (true) {
if (!def_val)
def_val = "";
printf("%s [%s]: ", question, def_val);
*buf = 0;
if (fgets(buf, sizeof(buf), stdin) == NULL)
errx(EXIT_FAILURE, _("Aborted."));
/* remove the newline at the end of buf. */
ans = buf;
while (isspace(*ans))
ans++;
len = strlen(ans);
while (len > 0 && isspace(ans[len - 1]))
len--;
if (len <= 0)
return NULL;
ans[len] = 0;
if (!strcasecmp(ans, blank))
return "";
if (check_gecos_string(NULL, ans) >= 0)
break;
}
cp = (char *)xmalloc(len + 1);
strcpy(cp, ans);
return cp;
}
/*
* 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(char *msg, char *gecos)
{
unsigned int i, c;
if (strlen(gecos) > MAX_FIELD_SIZE) {
if (msg)
warnx(_("field %s is too long"), msg);
else
warnx(_("field is too long"));
return -1;
}
for (i = 0; i < strlen(gecos); i++) {
c = gecos[i];
if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
if (msg)
warnx(_("%s: '%c' is not allowed"), msg, c);
else
warnx(_("'%c' is not allowed"), c);
return -1;
}
if (iscntrl(c)) {
if (msg)
warnx(_
("%s: control characters are not allowed"),
msg);
else
warnx(_("control characters are not allowed"));
return -1;
}
}
return 0;
}
/*
* set_changed_data () --
* incorporate the new data into the old finger info.
*/
static int set_changed_data(struct finfo *oldfp, struct finfo *newfp)
{
int changed = false;
if (newfp->full_name) {
oldfp->full_name = newfp->full_name;
changed = true;
}
if (newfp->office) {
oldfp->office = newfp->office;
changed = true;
}
if (newfp->office_phone) {
oldfp->office_phone = newfp->office_phone;
changed = true;
}
if (newfp->home_phone) {
oldfp->home_phone = newfp->home_phone;
changed = true;
}
return changed;
}
/*
* save_new_data () --
* save the given finger info in /etc/passwd.
* return zero on success.
*/
static int save_new_data(struct finfo *pinfo)
{
char *gecos;
int len;
/* null fields will confuse printf(). */
if (!pinfo->full_name)
pinfo->full_name = "";
if (!pinfo->office)
pinfo->office = "";
if (!pinfo->office_phone)
pinfo->office_phone = "";
if (!pinfo->home_phone)
pinfo->home_phone = "";
if (!pinfo->other)
pinfo->other = "";
/* create the new gecos string */
len = (strlen(pinfo->full_name) + strlen(pinfo->office) +
strlen(pinfo->office_phone) + strlen(pinfo->home_phone) +
strlen(pinfo->other) + 4);
gecos = (char *)xmalloc(len + 1);
sprintf(gecos, "%s,%s,%s,%s,%s", pinfo->full_name, pinfo->office,
pinfo->office_phone, pinfo->home_phone, pinfo->other);
/* remove trailing empty fields (but not subfields of pinfo->other) */
if (!pinfo->other[0]) {
while (len > 0 && gecos[len - 1] == ',')
len--;
gecos[len] = 0;
}
#ifdef HAVE_LIBUSER
if (set_value_libuser("chfn", pinfo->pw->pw_name, pinfo->pw->pw_uid,
LU_GECOS, gecos) < 0) {
#else /* HAVE_LIBUSER */
/* write the new struct passwd to the passwd file. */
pinfo->pw->pw_gecos = gecos;
if (setpwnam(pinfo->pw) < 0) {
warn("setpwnam failed");
#endif
printf(_
("Finger information *NOT* changed. Try again later.\n"));
return -1;
}
printf(_("Finger information changed.\n"));
return 0;
return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -143,6 +143,7 @@ enum {
TIME_SHORT,
TIME_FULL,
TIME_ISO,
TIME_ISO_SHORT,
};
/*
@ -349,6 +350,9 @@ static char *make_time(int mode, time_t time)
case TIME_ISO:
strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &tm);
break;
case TIME_ISO_SHORT:
strftime(buf, sizeof(buf), "%Y-%m-%d", &tm);
break;
default:
errx(EXIT_FAILURE, _("unsupported time type"));
}
@ -684,8 +688,8 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
if (strstr(pwd->pw_shell, "nologin"))
user->nologin = 1;
else if (pwd->pw_uid)
user->nologin = access("/etc/nologin", F_OK) == 0 ||
access("/var/run/nologin", F_OK) == 0;
user->nologin = access(_PATH_NOLOGIN, F_OK) == 0 ||
access(_PATH_VAR_NOLOGIN, F_OK) == 0;
break;
case COL_PWD_WARN:
if (shadow && shadow->sp_warn >= 0)
@ -693,7 +697,8 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
break;
case COL_PWD_EXPIR:
if (shadow && shadow->sp_expire >= 0)
user->pwd_expire = make_time(TIME_SHORT,
user->pwd_expire = make_time(ctl->time_mode == TIME_ISO ?
TIME_ISO_SHORT : ctl->time_mode,
shadow->sp_expire * 86400);
break;
case COL_PWD_CTIME:
@ -701,7 +706,8 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
* (especially in non-GMT timezones) would only serve
* to confuse */
if (shadow)
user->pwd_ctime = make_time(TIME_SHORT,
user->pwd_ctime = make_time(ctl->time_mode == TIME_ISO ?
TIME_ISO_SHORT : ctl->time_mode,
shadow->sp_lstchg * 86400);
break;
case COL_PWD_CTIME_MIN:
@ -1049,10 +1055,10 @@ static void fill_table(const void *u, const VISIT which, const int depth __attri
return;
}
#ifdef HAVE_LIBSYSTEMD
static void print_journal_tail(const char *journal_path, uid_t uid, size_t len)
static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
{
sd_journal *j;
char *match, *buf;
char *match, *timestamp;
uint64_t x;
time_t t;
const char *identifier, *pid, *message;
@ -1063,7 +1069,6 @@ static void print_journal_tail(const char *journal_path, uid_t uid, size_t len)
else
sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
buf = xmalloc(sizeof(char) * 16);
xasprintf(&match, "_UID=%d", uid);
sd_journal_add_match(j, match, 0);
@ -1083,21 +1088,18 @@ static void print_journal_tail(const char *journal_path, uid_t uid, size_t len)
sd_journal_get_realtime_usec(j, &x);
t = x / 1000000;
strftime(buf, 16, "%b %d %H:%M:%S", localtime(&t));
fprintf(stdout, "%s", buf);
timestamp = make_time(time_mode, t);
/* Get rid of journal entry field identifiers */
identifier = strchr(identifier, '=') + 1;
pid = strchr(pid, '=') + 1 ;
pid = strchr(pid, '=') + 1;
message = strchr(message, '=') + 1;
fprintf(stdout, " %s", identifier);
fprintf(stdout, "[%s]:", pid);
fprintf(stdout, "%s\n", message);
fprintf(stdout, "%s %s[%s]: %s\n", timestamp, identifier, pid,
message);
free(timestamp);
} while (sd_journal_next(j));
done:
free(buf);
free(match);
sd_journal_flush_matches(j);
sd_journal_close(j);
@ -1142,7 +1144,7 @@ static int print_user_table(struct lslogins_control *ctl)
print_pretty(tb);
#ifdef HAVE_LIBSYSTEMD
fprintf(stdout, _("\nLast logs:\n"));
print_journal_tail(ctl->journal_path, ctl->uid, 3);
print_journal_tail(ctl->journal_path, ctl->uid, 3, ctl->time_mode);
fputc('\n', stdout);
#endif
} else
@ -1175,16 +1177,25 @@ static void free_user(void *f)
free(u);
}
struct lslogins_timefmt {
const char *name;
int val;
};
static int parse_time_mode(const char *optarg)
{
struct lslogins_timefmt {
const char *name;
const int val;
};
static const struct lslogins_timefmt timefmts[] = {
{"iso", TIME_ISO},
{"full", TIME_FULL},
{"short", TIME_SHORT},
};
size_t i;
static struct lslogins_timefmt timefmts[] = {
{ "short", TIME_SHORT },
{ "full", TIME_FULL },
{ "iso", TIME_ISO },
};
for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
if (strcmp(timefmts[i].name, optarg) == 0)
return timefmts[i].val;
}
errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
}
static void __attribute__((__noreturn__)) usage(FILE *out)
{
@ -1240,8 +1251,7 @@ int main(int argc, char *argv[])
/* long only options. */
enum {
OPT_VER = CHAR_MAX + 1,
OPT_WTMP,
OPT_WTMP = CHAR_MAX + 1,
OPT_BTMP,
OPT_NOTRUNC,
OPT_NOHEAD,
@ -1299,7 +1309,7 @@ int main(int argc, char *argv[])
add_column(columns, ncolumns++, COL_UID);
add_column(columns, ncolumns++, COL_USER);
while ((c = getopt_long(argc, argv, "acfGg:hLl:no:prsuVxzZ",
while ((c = getopt_long(argc, argv, "acefGg:hLl:no:prsuVzZ",
longopts, NULL)) != -1) {
err_exclusive_options(c, longopts, excl, excl_st);
@ -1393,18 +1403,7 @@ int main(int argc, char *argv[])
ctl->noheadings = 1;
break;
case OPT_TIME_FMT:
{
size_t i;
for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
if (strcmp(timefmts[i].name, optarg) == 0) {
ctl->time_mode = timefmts[i].val;
break;
}
}
if (ctl->time_mode == TIME_INVALID)
usage(stderr);
}
ctl->time_mode = parse_time_mode(optarg);
break;
case 'V':
printf(UTIL_LINUX_VERSION);
@ -1432,7 +1431,7 @@ int main(int argc, char *argv[])
logins = argv[optind];
outmode = OUT_PRETTY;
} else if (argc != optind)
usage(stderr);
errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
scols_init_debug(0);