From aa45b344d374e4cb20747061b1648aead2543080 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Fri, 12 Dec 2014 22:46:04 +0000 Subject: [PATCH 01/18] lslogins: allow changing password changed and expiration time formats The password change and expiry has are marked with resolution of a day, so add a new short iso-8601 format. With this system admins can easily find users has not updated their password lately $ lslogins --time-format=iso --user --output=pwd-change,user | sort -n Signed-off-by: Sami Kerola --- login-utils/lslogins.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index 9929b1475..f050595ea 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -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")); } @@ -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: From f838efeb67553490638cf589d3c304b152554dda Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Fri, 12 Dec 2014 23:07:28 +0000 Subject: [PATCH 02/18] lslogins: make journald last logs time stamps to honor --time-format This makes by default the last logs to have year in output when necessary. Signed-off-by: Sami Kerola --- login-utils/lslogins.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index f050595ea..85fd7c834 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -1055,7 +1055,7 @@ 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; @@ -1069,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); @@ -1089,7 +1088,7 @@ 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)); + buf = make_time(time_mode, t); fprintf(stdout, "%s", buf); @@ -1148,7 +1147,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 From 1025d21efe5596b61fb3303d88edd53bd4d4414a Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sat, 13 Dec 2014 16:41:06 +0000 Subject: [PATCH 03/18] lslogins: tell why command failed Printing usage() without hint what is wrong does not help an user. Signed-off-by: Sami Kerola --- login-utils/lslogins.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index 85fd7c834..7fe6599d6 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -1437,7 +1437,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); From dfd4359bb0b41bdefc19f11c9c44faaf80561adc Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sat, 13 Dec 2014 16:58:37 +0000 Subject: [PATCH 04/18] lslogins: fix short options Usage is promising -e is an option alias of --export, so make it work. And get rid of -x that was accepted, but not in use. Long only enum member OPT_VER was probably a development time idea, that never got to be used. Signed-off-by: Sami Kerola --- login-utils/lslogins.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index 7fe6599d6..cacf83c1d 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -1245,8 +1245,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, @@ -1304,7 +1303,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); From cdf3896a0502b2773323ec293ccb4dfdbfc87cf8 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sat, 13 Dec 2014 17:11:04 +0000 Subject: [PATCH 05/18] lslogins: reject unknown time format arguments Signed-off-by: Sami Kerola --- login-utils/lslogins.c | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index cacf83c1d..5e1ef171c 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -1180,16 +1180,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) { @@ -1397,18 +1406,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); From 77006d95db0b06cda65d82360bbf1cce4a775094 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sat, 13 Dec 2014 17:49:20 +0000 Subject: [PATCH 06/18] lslogins: add space to systemd journal header and message This commit changes journal messages in individual user printout the following way. Dec 13 16:02:05 systemd[324]:Time has been changed (old) Dec 13 16:02:05 systemd[324]: Time has been changed (new) Signed-off-by: Sami Kerola --- login-utils/lslogins.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index 5e1ef171c..0c6a1fcbf 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -1058,7 +1058,7 @@ static void fill_table(const void *u, const VISIT which, const int depth __attri 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; @@ -1088,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; - buf = make_time(time_mode, 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); From 4ecc6dbb53c4d55ab8871b1ecccee28a94c5afbf Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sat, 13 Dec 2014 18:07:44 +0000 Subject: [PATCH 07/18] lslogins: use hardcoded paths from pathnames.h Signed-off-by: Sami Kerola --- include/pathnames.h | 1 + login-utils/lslogins.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/pathnames.h b/include/pathnames.h index 1cc4e15e6..0d21b980b 100644 --- a/include/pathnames.h +++ b/include/pathnames.h @@ -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" diff --git a/login-utils/lslogins.c b/login-utils/lslogins.c index 0c6a1fcbf..0e99529a9 100644 --- a/login-utils/lslogins.c +++ b/login-utils/lslogins.c @@ -688,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) From 2401633559d89d178334dad1da64cd51584d77d5 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sat, 13 Dec 2014 22:16:21 +0000 Subject: [PATCH 08/18] chfn: remove function prototypes Requires resuffling functions order they work without issues. Signed-off-by: Sami Kerola --- login-utils/chfn.c | 276 ++++++++++++++++++++++----------------------- 1 file changed, 134 insertions(+), 142 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 25f7e91e3..35d00f9c6 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -66,14 +66,6 @@ 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); - /* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */ #define MAX_FIELD_SIZE 256 @@ -93,99 +85,43 @@ static void __attribute__((__noreturn__)) usage(FILE *fp) exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS); } -int main(int argc, char **argv) +/* + * 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) { - uid_t uid; - struct finfo oldf, newf; - int interactive; + unsigned int i, c; - 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) - errx(EXIT_FAILURE, _("you (user %d) don't exist."), - uid); - } else { - parse_passwd(getpwnam(newf.username), &oldf); - if (!oldf.username) - errx(EXIT_FAILURE, _("user \"%s\" does not exist."), - newf.username); + if (strlen(gecos) > MAX_FIELD_SIZE) { + if (msg) + warnx(_("field %s is too long"), msg); + else + warnx(_("field is too long")); + return -1; } -#ifndef HAVE_LIBUSER - if (!(is_local(oldf.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) { - security_context_t user_context; - if (getprevcon(&user_context) < 0) - user_context = NULL; - errx(EXIT_FAILURE, - _("%s is not authorized to change " - "the finger info of %s"), - user_context ? : _("Unknown user context"), - oldf.username); - } + 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; } - if (setupDefaultContext(_PATH_PASSWD)) - 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 != oldf.pw->pw_uid) { -#else - if (uid != 0 && uid != 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); - -#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD) - if(!auth_pam("chfn", uid, oldf.username)) { - return EXIT_FAILURE; - } -#endif - - if (interactive) - ask_info(&oldf, &newf); - - if (!set_changed_data(&oldf, &newf)) { - printf(_("Finger information not changed.\n")); - return EXIT_SUCCESS; - } - - return save_new_data(&oldf) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + return 0; } /* @@ -306,19 +242,6 @@ static void parse_passwd(struct passwd *pw, struct finfo *pinfo) } } -/* - * 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. @@ -357,42 +280,16 @@ static char *prompt(char *question, char *def_val) } /* - * 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). + * ask_info () -- + * prompt the user for the finger information and store it. */ -static int check_gecos_string(char *msg, char *gecos) +static void ask_info(struct finfo *oldfp, struct finfo *newfp) { - 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; + 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"); } /* @@ -476,3 +373,98 @@ static int save_new_data(struct finfo *pinfo) printf(_("Finger information changed.\n")); return 0; } + +int main(int argc, char **argv) +{ + uid_t uid; + struct finfo oldf, newf; + int interactive; + + 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) + errx(EXIT_FAILURE, _("you (user %d) don't exist."), + uid); + } else { + parse_passwd(getpwnam(newf.username), &oldf); + if (!oldf.username) + errx(EXIT_FAILURE, _("user \"%s\" does not exist."), + newf.username); + } + +#ifndef HAVE_LIBUSER + if (!(is_local(oldf.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) { + security_context_t user_context; + if (getprevcon(&user_context) < 0) + user_context = NULL; + errx(EXIT_FAILURE, + _("%s is not authorized to change " + "the finger info of %s"), + user_context ? : _("Unknown user context"), + oldf.username); + } + } + if (setupDefaultContext(_PATH_PASSWD)) + 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 != oldf.pw->pw_uid) { +#else + if (uid != 0 && uid != 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); + +#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD) + if(!auth_pam("chfn", uid, oldf.username)) { + return EXIT_FAILURE; + } +#endif + + if (interactive) + ask_info(&oldf, &newf); + + if (!set_changed_data(&oldf, &newf)) { + printf(_("Finger information not changed.\n")); + return EXIT_SUCCESS; + } + + return save_new_data(&oldf) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} From 5a57c00af0dec86997ce7b1c97812ec603b83263 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 11:37:03 +0000 Subject: [PATCH 09/18] chfn: rewrite prompt() to use strutils The left and right white space trimming can be done with strutils.h [lr]trim_whitespace() functions. As a minor fix when user input exceeds maxium allowed gecos field length the remaining characters in stdin are purged so that re-prompting works correctly. Additionally the prompt() is made to add message to check_gecos_string(), so that there are less similar strings for translation project to deal. Signed-off-by: Sami Kerola --- login-utils/chfn.c | 59 ++++++++++++++++------------------------------ 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 35d00f9c6..ca6f45670 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -54,8 +54,6 @@ # include "auth.h" #endif -static char buf[1024]; - struct finfo { struct passwd *pw; char *username; @@ -90,34 +88,23 @@ static void __attribute__((__noreturn__)) usage(FILE *fp) * 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) +static int check_gecos_string(const char *msg, char *gecos) { unsigned int i, c; + const size_t len = strlen(gecos); - if (strlen(gecos) > MAX_FIELD_SIZE) { - if (msg) - warnx(_("field %s is too long"), msg); - else - warnx(_("field is too long")); + if (MAX_FIELD_SIZE < len) { + warnx(_("field %s is too long"), msg); return -1; } - - for (i = 0; i < strlen(gecos); i++) { + for (i = 0; i < len; 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); + warnx(_("%s: '%c' is not allowed"), msg, c); return -1; } if (iscntrl(c)) { - if (msg) - warnx(_ - ("%s: control characters are not allowed"), - msg); - else - warnx(_("control characters are not allowed")); + warnx(_("%s: control characters are not allowed"), msg); return -1; } } @@ -246,37 +233,31 @@ static void parse_passwd(struct passwd *pw, struct finfo *pinfo) * 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 *prompt(const char *question, char *def_val) { - static char *blank = "none"; int len; - char *ans, *cp; + char *ans; + char buf[MAX_FIELD_SIZE + 2]; + if (!def_val) + def_val = ""; while (true) { - if (!def_val) - def_val = ""; printf("%s [%s]: ", question, def_val); - *buf = 0; + __fpurge(stdin); 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) + /* remove white spaces from string end */ + ltrim_whitespace((unsigned char *) ans); + len = rtrim_whitespace((unsigned char *) ans); + if (len == 0) return NULL; - ans[len] = 0; - if (!strcasecmp(ans, blank)) + if (!strcasecmp(ans, "none")) return ""; - if (check_gecos_string(NULL, ans) >= 0) + if (check_gecos_string(question, ans) >= 0) break; } - cp = (char *)xmalloc(len + 1); - strcpy(cp, ans); - return cp; + return xstrdup(ans); } /* From 1b7a19ebbb61c608265b552f4426bfe01373b1d0 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 12:28:26 +0000 Subject: [PATCH 10/18] chfn: use xasprintf() rather than bunch of strlen() and malloc() calls Signed-off-by: Sami Kerola --- login-utils/chfn.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index ca6f45670..4746927c8 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -324,12 +324,8 @@ static int save_new_data(struct finfo *pinfo) 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); + len = xasprintf(&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]) { @@ -351,6 +347,7 @@ static int save_new_data(struct finfo *pinfo) ("Finger information *NOT* changed. Try again later.\n")); return -1; } + free(gecos); printf(_("Finger information changed.\n")); return 0; } From 496083bad6c5668d2f615906eb877d63cf5d7c6c Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 12:31:11 +0000 Subject: [PATCH 11/18] chfn: fix usage() regression Commit db433bf737a5fd4e1c7cca5e3603934743eebd1c changed -u for --help to -h, that is not true. The -h is short hand for --home-phone. And the --version is accompanied with -v not -V. Signed-off-by: Sami Kerola --- login-utils/chfn.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 4746927c8..9833591e8 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -77,8 +77,8 @@ static void __attribute__((__noreturn__)) usage(FILE *fp) fputs(_(" -p, --office-phone office phone number\n"), fp); fputs(_(" -h, --home-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); } From 58985f6712d4f869eb68bfe1fee8ca2074cf226f Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 13:28:57 +0000 Subject: [PATCH 12/18] chfn: simplify parse_passwd() by using strsep() Signed-off-by: Sami Kerola --- login-utils/chfn.c | 49 +++++++++++++--------------------------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 9833591e8..7ba1a5180 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -191,42 +191,21 @@ static int parse_argv(int argc, char *argv[], struct finfo *pinfo) 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; - } + if (!pw) + return; + 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); + /* extract known fields */ + pinfo->full_name = strsep(&gecos, ","); + pinfo->office = strsep(&gecos, ","); + pinfo->office_phone = strsep(&gecos, ","); + pinfo->home_phone = strsep(&gecos, ","); + /* extra fields contain site-specific information, and can + * not be changed by this version of chfn. */ + pinfo->other = strsep(&gecos, ","); } /* From d5fdba03469737c3daa9b03940a91fe13c79e86e Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 13:48:17 +0000 Subject: [PATCH 13/18] chfn: add minimalistic struct chfn_control Signed-off-by: Sami Kerola --- login-utils/chfn.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 7ba1a5180..81abd11b3 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -64,6 +64,11 @@ struct finfo { char *other; }; +struct chfn_control { + unsigned int + 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 @@ -116,10 +121,9 @@ static int check_gecos_string(const char *msg, char *gecos) * 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) +static void parse_argv(struct chfn_control *ctl, 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'}, @@ -132,7 +136,6 @@ static int parse_argv(int argc, char *argv[], struct finfo *pinfo) }; optind = 0; - info_given = false; while (true) { c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options, &index); @@ -149,7 +152,7 @@ static int parse_argv(int argc, char *argv[], struct finfo *pinfo) if (!optarg) usage(stderr); /* ok, we were given an argument */ - info_given = true; + ctl->interactive = 0; /* now store the argument */ switch (c) { @@ -181,7 +184,7 @@ static int parse_argv(int argc, char *argv[], struct finfo *pinfo) usage(stderr); pinfo->username = argv[optind]; } - return !info_given; + return; } /* @@ -335,7 +338,9 @@ 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 */ @@ -358,7 +363,7 @@ int main(int argc, char **argv) memset(&oldf, 0, sizeof(oldf)); memset(&newf, 0, sizeof(newf)); - interactive = parse_argv(argc, argv, &newf); + parse_argv(&ctl, argc, argv, &newf); if (!newf.username) { parse_passwd(getpwuid(uid), &oldf); if (!oldf.username) @@ -415,7 +420,7 @@ int main(int argc, char **argv) } #endif - if (interactive) + if (ctl.interactive) ask_info(&oldf, &newf); if (!set_changed_data(&oldf, &newf)) { From e4efecc4dba2d2efd17f89cdddf1b48c87909c1b Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 13:56:32 +0000 Subject: [PATCH 14/18] chfn: clean up parse_argv() Use switch() case ?: for all option parsing, as in most of the other source files. Signed-off-by: Sami Kerola --- login-utils/chfn.c | 48 +++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 81abd11b3..801bdbe19 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -121,11 +121,11 @@ static int check_gecos_string(const char *msg, char *gecos) * 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[], struct finfo *pinfo) +static void parse_argv(struct chfn_control *ctl, int argc, char *argv[], + struct finfo *pinfo) { - int index, c, status; - - static struct option long_options[] = { + 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'}, @@ -135,49 +135,37 @@ static void parse_argv(struct chfn_control *ctl, int argc, char *argv[], struct {NULL, no_argument, 0, '0'}, }; - optind = 0; - 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 */ - ctl->interactive = 0; - - /* now store the argument */ + while ((c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options, + &index)) != -1) { switch (c) { case 'f': pinfo->full_name = optarg; - status = check_gecos_string(_("Name"), optarg); + status += check_gecos_string(_("Name"), optarg); break; case 'o': pinfo->office = optarg; - status = check_gecos_string(_("Office"), optarg); + status += check_gecos_string(_("Office"), optarg); break; case 'p': pinfo->office_phone = optarg; - status = check_gecos_string(_("Office Phone"), optarg); + status += check_gecos_string(_("Office Phone"), optarg); break; case 'h': pinfo->home_phone = optarg; - status = check_gecos_string(_("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); } - if (status != 0) - exit(EXIT_FAILURE); + ctl->interactive = 0; } + if (status != 0) + exit(EXIT_FAILURE); /* done parsing arguments. check for a username. */ if (optind < argc) { if (optind + 1 < argc) From 5fe1c32f61d2ba0d1ec91767bf11a978a934f6a3 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 17:44:11 +0000 Subject: [PATCH 15/18] chfn: move new and old finger structs to chfn control struct This change is a little bit messy, and requires a comment the struct finfo should not have 'struct passwd *pw' as it's member. The earlier struct design would have been burden to maintain, and confusing to use. Signed-off-by: Sami Kerola --- login-utils/chfn.c | 165 +++++++++++++++++++++------------------------ 1 file changed, 76 insertions(+), 89 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 801bdbe19..f74ed228d 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -55,8 +55,6 @@ #endif struct finfo { - struct passwd *pw; - char *username; char *full_name; char *office; char *office_phone; @@ -65,6 +63,13 @@ struct finfo { }; 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 interactive:1; /* whether to prompt for fields or not */ }; @@ -121,8 +126,7 @@ static int check_gecos_string(const char *msg, char *gecos) * 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[], - struct finfo *pinfo) +static void parse_argv(struct chfn_control *ctl, int argc, char **argv) { int index, c, status = 0; static const struct option long_options[] = { @@ -139,19 +143,19 @@ static void parse_argv(struct chfn_control *ctl, int argc, char *argv[], &index)) != -1) { switch (c) { case 'f': - pinfo->full_name = optarg; + ctl->newf.full_name = optarg; status += check_gecos_string(_("Name"), optarg); break; case 'o': - pinfo->office = optarg; + ctl->newf.office = optarg; status += check_gecos_string(_("Office"), optarg); break; case 'p': - pinfo->office_phone = optarg; + ctl->newf.office_phone = optarg; status += check_gecos_string(_("Office Phone"), optarg); break; case 'h': - pinfo->home_phone = optarg; + ctl->newf.home_phone = optarg; status += check_gecos_string(_("Home Phone"), optarg); break; case 'v': @@ -170,7 +174,7 @@ static void parse_argv(struct chfn_control *ctl, int argc, char *argv[], if (optind < argc) { if (optind + 1 < argc) usage(stderr); - pinfo->username = argv[optind]; + ctl->username = argv[optind]; } return; } @@ -179,24 +183,22 @@ static void parse_argv(struct chfn_control *ctl, int argc, char *argv[], * 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) +static void parse_passwd(struct chfn_control *ctl) { char *gecos; - if (!pw) + if (!ctl->pw) return; - 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); + gecos = xstrdup(ctl->pw->pw_gecos); /* extract known fields */ - pinfo->full_name = strsep(&gecos, ","); - pinfo->office = strsep(&gecos, ","); - pinfo->office_phone = strsep(&gecos, ","); - pinfo->home_phone = strsep(&gecos, ","); + 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. */ - pinfo->other = strsep(&gecos, ","); + ctl->oldf.other = strsep(&gecos, ","); } /* @@ -234,12 +236,12 @@ static char *prompt(const char *question, char *def_val) * ask_info () -- * prompt the user for the finger information and store it. */ -static void ask_info(struct finfo *oldfp, struct finfo *newfp) +static void ask_info(struct chfn_control *ctl) { - 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); + ctl->newf.full_name = prompt(_("Name"), ctl->oldf.full_name); + ctl->newf.office = prompt(_("Office"), ctl->oldf.office); + ctl->newf.office_phone = prompt(_("Office Phone"), ctl->oldf.office_phone); + ctl->newf.home_phone = prompt(_("Home Phone"), ctl->oldf.home_phone); printf("\n"); } @@ -247,27 +249,26 @@ static void ask_info(struct finfo *oldfp, struct finfo *newfp) * set_changed_data () -- * incorporate the new data into the old finger info. */ -static int set_changed_data(struct finfo *oldfp, struct finfo *newfp) +static int set_changed_data(struct chfn_control *ctl) { int changed = false; - if (newfp->full_name) { - oldfp->full_name = newfp->full_name; + if (ctl->newf.full_name) changed = true; - } - if (newfp->office) { - oldfp->office = newfp->office; + else + ctl->newf.full_name = ctl->oldf.full_name; + if (ctl->newf.office) changed = true; - } - if (newfp->office_phone) { - oldfp->office_phone = newfp->office_phone; + else + ctl->newf.office = ctl->oldf.office; + if (ctl->newf.office_phone) changed = true; - } - if (newfp->home_phone) { - oldfp->home_phone = newfp->home_phone; + else + ctl->newf.office_phone = ctl->oldf.office_phone; + if (ctl->newf.home_phone) changed = true; - } - + else + ctl->newf.home_phone = ctl->oldf.home_phone; return changed; } @@ -276,41 +277,41 @@ static int set_changed_data(struct finfo *oldfp, struct finfo *newfp) * save the given finger info in /etc/passwd. * return zero on success. */ -static int save_new_data(struct finfo *pinfo) +static int save_new_data(struct chfn_control *ctl) { 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 = ""; + if (!ctl->newf.full_name) + ctl->newf.full_name = ""; + if (!ctl->newf.office) + ctl->newf.office = ""; + if (!ctl->newf.office_phone) + ctl->newf.office_phone = ""; + if (!ctl->newf.home_phone) + ctl->newf.home_phone = ""; + if (!ctl->newf.other) + ctl->newf.other = ""; /* create the new gecos string */ - len = xasprintf(&gecos, "%s,%s,%s,%s,%s", pinfo->full_name, pinfo->office, - pinfo->office_phone, pinfo->home_phone, pinfo->other); + 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 pinfo->other) */ - if (!pinfo->other[0]) { + /* remove trailing empty fields (but not subfields of ctl->newf.other) */ + if (!ctl->newf.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, + 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. */ - pinfo->pw->pw_gecos = gecos; - if (setpwnam(pinfo->pw) < 0) { + ctl->pw->pw_gecos = gecos; + if (setpwnam(ctl->pw) < 0) { warn("setpwnam failed"); #endif printf(_ @@ -325,7 +326,6 @@ static int save_new_data(struct finfo *pinfo) int main(int argc, char **argv) { uid_t uid; - struct finfo oldf, newf; struct chfn_control ctl = { .interactive = 1 }; @@ -335,44 +335,31 @@ int main(int argc, char **argv) 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)); - parse_argv(&ctl, argc, argv, &newf); - if (!newf.username) { - parse_passwd(getpwuid(uid), &oldf); - if (!oldf.username) + 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; @@ -380,7 +367,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)) @@ -391,30 +378,30 @@ 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 (ctl.interactive) - ask_info(&oldf, &newf); + ask_info(&ctl); - if (!set_changed_data(&oldf, &newf)) { + if (!set_changed_data(&ctl)) { printf(_("Finger information not changed.\n")); return EXIT_SUCCESS; } - return save_new_data(&oldf) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } From d9e1ac99e420f2521849e336a4e57cce1b875241 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Wed, 17 Dec 2014 22:28:03 +0000 Subject: [PATCH 16/18] chfn: rename prompt() to ask_new_field() Signed-off-by: Sami Kerola --- login-utils/chfn.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index f74ed228d..fabe308f9 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -202,10 +202,11 @@ static void parse_passwd(struct chfn_control *ctl) } /* - * prompt () -- + * ask_new_field () -- * ask the user for a given field and check that the string is legal. */ -static char *prompt(const char *question, char *def_val) +static char *ask_new_field(struct chfn_control *ctl, const char *question, + char *def_val) { int len; char *ans; @@ -238,10 +239,10 @@ static char *prompt(const char *question, char *def_val) */ static void ask_info(struct chfn_control *ctl) { - ctl->newf.full_name = prompt(_("Name"), ctl->oldf.full_name); - ctl->newf.office = prompt(_("Office"), ctl->oldf.office); - ctl->newf.office_phone = prompt(_("Office Phone"), ctl->oldf.office_phone); - ctl->newf.home_phone = prompt(_("Home Phone"), ctl->oldf.home_phone); + ctl->newf.full_name = ask_new_field(ctl, _("Name"), ctl->oldf.full_name); + ctl->newf.office = ask_new_field(ctl, _("Office"), ctl->oldf.office); + ctl->newf.office_phone = ask_new_field(ctl, _("Office Phone"), ctl->oldf.office_phone); + ctl->newf.home_phone = ask_new_field(ctl, _("Home Phone"), ctl->oldf.home_phone); printf("\n"); } From f723cbf544a7eac2927634f2cb6d802437a2d519 Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Wed, 17 Dec 2014 22:28:49 +0000 Subject: [PATCH 17/18] chfn: remove set_changed_data() and add add_missing() The add_missing() and find_field() functions are needed when input data is incomplete, such as in case when chfn is instructed to change only selected fields with command line options. Signed-off-by: Sami Kerola --- login-utils/chfn.c | 79 +++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index fabe308f9..50beea3ed 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -71,6 +71,7 @@ struct chfn_control { * In the end, "newf" is folded into "oldf". */ struct finfo oldf, newf; unsigned int + changed:1, /* is change requested */ interactive:1; /* whether to prompt for fields or not */ }; @@ -166,6 +167,7 @@ static void parse_argv(struct chfn_control *ctl, int argc, char **argv) default: usage(stderr); } + ctl->changed = 1; ctl->interactive = 0; } if (status != 0) @@ -224,12 +226,15 @@ static char *ask_new_field(struct chfn_control *ctl, const char *question, ltrim_whitespace((unsigned char *) ans); len = rtrim_whitespace((unsigned char *) ans); if (len == 0) - return NULL; - if (!strcasecmp(ans, "none")) - return ""; + 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); } @@ -247,30 +252,30 @@ static void ask_info(struct chfn_control *ctl) } /* - * set_changed_data () -- - * incorporate the new data into the old finger info. + * find_field () -- + * find field value in uninteractive mode; can be new, old, or blank */ -static int set_changed_data(struct chfn_control *ctl) +static char *find_field(char *nf, char *of) { - int changed = false; + if (nf) + return nf; + if (of) + return of; + return xstrdup(""); +} - if (ctl->newf.full_name) - changed = true; - else - ctl->newf.full_name = ctl->oldf.full_name; - if (ctl->newf.office) - changed = true; - else - ctl->newf.office = ctl->oldf.office; - if (ctl->newf.office_phone) - changed = true; - else - ctl->newf.office_phone = ctl->oldf.office_phone; - if (ctl->newf.home_phone) - changed = true; - else - ctl->newf.home_phone = ctl->oldf.home_phone; - return changed; +/* + * 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"); } /* @@ -283,24 +288,16 @@ static int save_new_data(struct chfn_control *ctl) char *gecos; int len; - /* null fields will confuse printf(). */ - if (!ctl->newf.full_name) - ctl->newf.full_name = ""; - if (!ctl->newf.office) - ctl->newf.office = ""; - if (!ctl->newf.office_phone) - ctl->newf.office_phone = ""; - if (!ctl->newf.home_phone) - ctl->newf.home_phone = ""; - if (!ctl->newf.other) - ctl->newf.other = ""; - /* 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); + 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[0]) { + if (!ctl->newf.other) { while (len > 0 && gecos[len - 1] == ',') len--; gecos[len] = 0; @@ -398,8 +395,10 @@ int main(int argc, char **argv) if (ctl.interactive) ask_info(&ctl); + else + add_missing(&ctl); - if (!set_changed_data(&ctl)) { + if (!ctl.changed) { printf(_("Finger information not changed.\n")); return EXIT_SUCCESS; } From e88f005949c43a5ddb5b9284552a15808c65b71a Mon Sep 17 00:00:00 2001 From: Sami Kerola Date: Sun, 14 Dec 2014 16:45:50 +0000 Subject: [PATCH 18/18] chfn: make command to obey login.defs CHFN_RESTRICT instructions Reference: http://man7.org/linux/man-pages/man5/login.defs.5.html Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=138519 Signed-off-by: Sami Kerola --- login-utils/Makemodule.am | 6 ++- login-utils/chfn.c | 83 +++++++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am index 47291afd3..e1f88c377 100644 --- a/login-utils/Makemodule.am +++ b/login-utils/Makemodule.am @@ -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) diff --git a/login-utils/chfn.c b/login-utils/chfn.c index 50beea3ed..b42ad4f27 100644 --- a/login-utils/chfn.c +++ b/login-utils/chfn.c @@ -40,6 +40,7 @@ #include "setpwnam.h" #include "strutils.h" #include "xalloc.h" +#include "logindefs.h" #ifdef HAVE_LIBSELINUX # include @@ -71,6 +72,10 @@ struct chfn_control { * 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 */ }; @@ -144,18 +149,26 @@ static void parse_argv(struct chfn_control *ctl, int argc, char **argv) &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; @@ -238,17 +251,70 @@ static char *ask_new_field(struct chfn_control *ctl, const char *question, 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) { - ctl->newf.full_name = ask_new_field(ctl, _("Name"), ctl->oldf.full_name); - ctl->newf.office = ask_new_field(ctl, _("Office"), ctl->oldf.office); - ctl->newf.office_phone = ask_new_field(ctl, _("Office Phone"), ctl->oldf.office_phone); - ctl->newf.home_phone = ask_new_field(ctl, _("Home Phone"), ctl->oldf.home_phone); - printf("\n"); + 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'); } /* @@ -335,6 +401,9 @@ int main(int argc, char **argv) atexit(close_stdout); uid = getuid(); + /* check /etc/login.defs CHFN_RESTRICT */ + get_login_defs(&ctl); + parse_argv(&ctl, argc, argv); if (!ctl.username) { ctl.pw = getpwuid(uid); @@ -395,8 +464,8 @@ int main(int argc, char **argv) if (ctl.interactive) ask_info(&ctl); - else - add_missing(&ctl); + + add_missing(&ctl); if (!ctl.changed) { printf(_("Finger information not changed.\n"));