| /* |
| * chfn.c -- change your finger information |
| * (c) 1994 by salvatore valente <svalente@athena.mit.edu> |
| * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com> |
| * |
| * this program is free software. you can redistribute it and |
| * modify it under the terms of the gnu general public license. |
| * there is no warranty. |
| * |
| * $Author: aebr $ |
| * $Revision: 1.18 $ |
| * $Date: 1998/06/11 22:30:11 $ |
| * |
| * Updated Thu Oct 12 09:19:26 1995 by faith@cs.unc.edu with security |
| * patches from Zefram <A.Main@dcs.warwick.ac.uk> |
| * |
| * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de, |
| * to remove trailing empty fields. Oct 5, 96. |
| * |
| * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> |
| * - added Native Language Support |
| */ |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <pwd.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "c.h" |
| #include "env.h" |
| #include "closestream.h" |
| #include "islocal.h" |
| #include "nls.h" |
| #include "setpwnam.h" |
| #include "strutils.h" |
| #include "xalloc.h" |
| #include "logindefs.h" |
| |
| #include "ch-common.h" |
| |
| #ifdef HAVE_LIBSELINUX |
| # include <selinux/selinux.h> |
| # include <selinux/av_permissions.h> |
| # include "selinux_utils.h" |
| #endif |
| |
| #ifdef HAVE_LIBUSER |
| # include <libuser/user.h> |
| # include "libuser.h" |
| #elif CHFN_CHSH_PASSWORD |
| # include "auth.h" |
| #endif |
| |
| struct finfo { |
| char *full_name; |
| char *office; |
| char *office_phone; |
| char *home_phone; |
| char *other; |
| }; |
| |
| struct chfn_control { |
| struct passwd *pw; |
| char *username; |
| /* "oldf" Contains the users original finger information. |
| * "newf" Contains the changed finger information, and contains |
| * NULL in fields that haven't been changed. |
| * In the end, "newf" is folded into "oldf". */ |
| struct finfo oldf, newf; |
| unsigned int |
| allow_fullname:1, /* The login.defs restriction */ |
| allow_room:1, /* see: man login.defs(5) */ |
| allow_work:1, /* and look for CHFN_RESTRICT */ |
| allow_home:1, /* keyword for these four. */ |
| changed:1, /* is change requested */ |
| interactive:1; /* whether to prompt for fields or not */ |
| }; |
| |
| /* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */ |
| #define MAX_FIELD_SIZE 256 |
| |
| static void __attribute__((__noreturn__)) usage(FILE *fp) |
| { |
| fputs(USAGE_HEADER, fp); |
| fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name); |
| |
| fputs(USAGE_SEPARATOR, fp); |
| fputs(_("Change your finger information.\n"), fp); |
| |
| fputs(USAGE_OPTIONS, fp); |
| fputs(_(" -f, --full-name <full-name> real name\n"), fp); |
| fputs(_(" -o, --office <office> office number\n"), 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(_(" -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) |
| { |
| const size_t len = strlen(gecos); |
| |
| if (MAX_FIELD_SIZE < len) { |
| warnx(_("field %s is too long"), msg); |
| return -1; |
| } |
| if (illegal_passwd_chars(gecos)) { |
| warnx(_("%s: has illegal characters"), gecos); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * parse_argv () -- |
| * parse the command line arguments. |
| * returns true if no information beyond the username was given. |
| */ |
| static void parse_argv(struct chfn_control *ctl, int argc, char **argv) |
| { |
| int index, c, status = 0; |
| static const struct option long_options[] = { |
| {"full-name", required_argument, 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 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); |
| uid = getuid(); |
| |
| /* check /etc/login.defs CHFN_RESTRICT */ |
| get_login_defs(&ctl); |
| |
| parse_argv(&ctl, argc, argv); |
| if (!ctl.username) { |
| ctl.pw = getpwuid(uid); |
| if (!ctl.pw) |
| errx(EXIT_FAILURE, _("you (user %d) don't exist."), |
| uid); |
| ctl.username = ctl.pw->pw_name; |
| } else { |
| ctl.pw = getpwnam(ctl.username); |
| if (!ctl.pw) |
| errx(EXIT_FAILURE, _("user \"%s\" does not exist."), |
| ctl.username); |
| } |
| parse_passwd(&ctl); |
| #ifndef HAVE_LIBUSER |
| if (!(is_local(ctl.username))) |
| errx(EXIT_FAILURE, _("can only change local entries")); |
| #endif |
| |
| #ifdef HAVE_LIBSELINUX |
| if (is_selinux_enabled() > 0) { |
| if (uid == 0) { |
| if (checkAccess(ctl.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"), |
| ctl.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 != ctl.pw->pw_uid) { |
| #else |
| if (uid != 0 && uid != ctl.pw->pw_uid) { |
| #endif |
| errno = EACCES; |
| err(EXIT_FAILURE, _("running UID doesn't match UID of user we're " |
| "altering, change denied")); |
| } |
| |
| printf(_("Changing finger information for %s.\n"), ctl.username); |
| |
| #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD) |
| if (!auth_pam("chfn", uid, ctl.username)) { |
| return EXIT_FAILURE; |
| } |
| #endif |
| |
| if (ctl.interactive) |
| ask_info(&ctl); |
| |
| add_missing(&ctl); |
| |
| if (!ctl.changed) { |
| printf(_("Finger information not changed.\n")); |
| return EXIT_SUCCESS; |
| } |
| |
| return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |