]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/chfn.c
libblkid: check status for the current CDROM slot
[thirdparty/util-linux.git] / login-utils / chfn.c
index 73a9b8918633c590f93fc8055e2ec5603783419e..b7395552bba90578fc74bf704f9b0d263ac6d30d 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *   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.
  * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de,
  * to remove trailing empty fields.  Oct 5, 96.
  *
- *  1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org>
+ *  1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
  *  - added Native Language Support
- *    
- *
  */
 
-#define _BSD_SOURCE           /* for strcasecmp() */
-
-#include <sys/types.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdbool.h>
 #include <stdio.h>
-#include <string.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
 #include <unistd.h>
-#include <pwd.h>
-#include <errno.h>
-#include <ctype.h>
-#include <getopt.h>
-#include "my_crypt.h"
-#include "nls.h"
-#include "env.h"
 
-#if REQUIRE_PASSWORD && USE_PAM
-#include <security/pam_appl.h>
-#include <security/pam_misc.h>
-#endif
+#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"
 
-extern int is_local(char *);
+#include "ch-common.h"
 
-#undef P
-#if __STDC__
-#define P(foo) foo
-#else
-#define P(foo) ()
+#ifdef HAVE_LIBSELINUX
+# include <selinux/selinux.h>
+# include "selinux_utils.h"
 #endif
 
-typedef unsigned char boolean;
-#define false 0
-#define true 1
-
-static char *whoami;
+#ifdef HAVE_LIBUSER
+# include <libuser/user.h>
+# include "libuser.h"
+#elif CHFN_CHSH_PASSWORD
+# include "auth.h"
+#endif
 
-static char buf[1024];
+#ifdef HAVE_LIBREADLINE
+# define _FUNCTION_DEF
+# include <readline/readline.h>
+#endif
 
 struct finfo {
-    struct passwd *pw;
-    char *username;
-    char *full_name;
-    char *office;
-    char *office_phone;
-    char *home_phone;
-    char *other;
+       char *full_name;
+       char *office;
+       char *office_phone;
+       char *home_phone;
+       char *other;
 };
 
-static boolean parse_argv P((int argc, char *argv[], struct finfo *pinfo));
-static void usage P((FILE *fp));
-static void parse_passwd P((struct passwd *pw, struct finfo *pinfo));
-static void ask_info P((struct finfo *oldfp, struct finfo *newfp));
-static char *prompt P((char *question, char *def_val));
-static int check_gecos_string P((char *msg, char *gecos));
-static boolean set_changed_data P((struct finfo *oldfp, struct finfo *newfp));
-static int save_new_data P((struct finfo *pinfo));
-static void *xmalloc P((int bytes));
-
-extern int setpwnam P((struct passwd *pwd));
-
-#define memzero(ptr, size) memset((char *) ptr, 0, size)
+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
 
-int main (int argc, char **argv) {
-    char *cp;
-    uid_t uid;
-    struct finfo oldf, newf;
-    boolean interactive;
-    int status;
-#if REQUIRE_PASSWORD && USE_PAM
-    pam_handle_t *pamh = NULL;
-    int retcode;
-    struct pam_conv conv = { misc_conv, NULL };
-#endif
+static void __attribute__((__noreturn__)) usage(void)
+{
+       FILE *fp = stdout;
+       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);
+       printf( " -u, --help                   %s\n", USAGE_OPTSTR_HELP);
+       printf( " -v, --version                %s\n", USAGE_OPTSTR_VERSION);
+       printf(USAGE_MAN_TAIL("chfn(1)"));
+       exit(EXIT_SUCCESS);
+}
 
-    sanitize_env();
-    setlocale(LC_ALL, "");     /* both for messages and for iscntrl() below */
-    bindtextdomain(PACKAGE, LOCALEDIR);
-    textdomain(PACKAGE);
-
-    /* whoami is the program name for error messages */
-    whoami = argv[0];
-    if (! whoami) whoami = "chfn";
-    for (cp = whoami; *cp; cp++)
-       if (*cp == '/') whoami = cp + 1;
-
-    /*
-     * "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 ();
-    memzero (&oldf, sizeof (oldf));
-    memzero (&newf, sizeof (newf));
-
-    interactive = parse_argv (argc, argv, &newf);
-    if (! newf.username) {
-       parse_passwd (getpwuid (uid), &oldf);
-       if (! oldf.username) {
-           fprintf (stderr, _("%s: you (user %d) don't exist.\n"), whoami, uid);
-           return (-1); }
-    }
-    else {
-       parse_passwd (getpwnam (newf.username), &oldf);
-       if (! oldf.username) {
-           cp = newf.username;
-           fprintf (stderr, _("%s: user \"%s\" does not exist.\n"), whoami, cp);
-           return (-1); }
-    }
-
-    if (!(is_local(oldf.username))) {
-       fprintf (stderr, _("%s: can only change local entries; use yp%s instead.\n"),
-           whoami, whoami);
-       exit(1);
-    }
-
-    /* Reality check */
-    if (uid != 0 && uid != oldf.pw->pw_uid) {
-       errno = EACCES;
-       perror (whoami);
-       return (-1);
-    }
-
-    printf (_("Changing finger information for %s.\n"), oldf.username);
-
-#if REQUIRE_PASSWORD
-# if USE_PAM
-    if(uid != 0) {
-        if (pam_start("chfn", oldf.username, &conv, &pamh)) {
-           puts(_("Password error."));
-           exit(1);
-       }
-        if (pam_authenticate(pamh, 0)) {
-           puts(_("Password error."));
-           exit(1);
-       }
-        retcode = pam_acct_mgmt(pamh, 0);
-        if (retcode == PAM_NEW_AUTHTOK_REQD) {
-           retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
-        } else if (retcode) {
-           puts(_("Password error."));
-           exit(1);
-       }
-        if (pam_setcred(pamh, 0)) {
-           puts(_("Password error."));
-           exit(1);
+/*
+ *  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;
        }
-        /* no need to establish a session; this isn't a session-oriented
-         * activity... */
-    }
-# else /* USE_PAM */
-    /* require password, unless root */
-    if(uid != 0 && oldf.pw->pw_passwd && oldf.pw->pw_passwd[0]) {
-       char *pwdstr = getpass(_("Password: "));
-       if(strncmp(oldf.pw->pw_passwd,
-                  crypt(pwdstr, oldf.pw->pw_passwd), 13)) {
-           puts(_("Incorrect password."));
-           exit(1);
+       if (illegal_passwd_chars(gecos)) {
+               warnx(_("%s: has illegal characters"), gecos);
+               return -1;
        }
-    }
-# endif /* USE_PAM */
-#endif /* REQUIRE_PASSWORD */
-
-
-    if (interactive) ask_info (&oldf, &newf);
-
-    if (! set_changed_data (&oldf, &newf)) {
-       printf (_("Finger information not changed.\n"));
        return 0;
-    }
-    status = save_new_data (&oldf);
-    return status;
 }
 
 /*
@@ -207,243 +135,226 @@ int main (int argc, char **argv) {
  *     parse the command line arguments.
  *     returns true if no information beyond the username was given.
  */
-static boolean parse_argv (argc, argv, pinfo)
-    int argc;
-    char *argv[];
-    struct finfo *pinfo;
+static void parse_argv(struct chfn_control *ctl, int argc, char **argv)
 {
-    int index, c, status;
-    boolean 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 == EOF) break;
-       /* version?  output version and exit. */
-       if (c == 'v') {
-           printf ("%s\n", util_linux_version);
-           exit (0);
-       }
-       if (c == 'u') {
-           usage (stdout);
-           exit (0);
+       int index, c, status = 0;
+       static const struct option long_options[] = {
+               { "full-name",    required_argument, NULL, 'f' },
+               { "office",       required_argument, NULL, 'o' },
+               { "office-phone", required_argument, NULL, 'p' },
+               { "home-phone",   required_argument, NULL, 'h' },
+               { "help",         no_argument,       NULL, 'u' },
+               { "version",      no_argument,       NULL, 'v' },
+               { NULL, 0, NULL, 0 },
+       };
+
+       while ((c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
+                               &index)) != -1) {
+               switch (c) {
+               case 'f':
+                       if (!ctl->allow_fullname)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Name"));
+                       ctl->newf.full_name = optarg;
+                       status += check_gecos_string(_("Name"), optarg);
+                       break;
+               case 'o':
+                       if (!ctl->allow_room)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office"));
+                       ctl->newf.office = optarg;
+                       status += check_gecos_string(_("Office"), optarg);
+                       break;
+               case 'p':
+                       if (!ctl->allow_work)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office Phone"));
+                       ctl->newf.office_phone = optarg;
+                       status += check_gecos_string(_("Office Phone"), optarg);
+                       break;
+               case 'h':
+                       if (!ctl->allow_home)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Home Phone"));
+                       ctl->newf.home_phone = optarg;
+                       status += check_gecos_string(_("Home Phone"), optarg);
+                       break;
+               case 'v':
+                       print_version(EXIT_SUCCESS);
+               case 'u':
+                       usage();
+               default:
+                       errtryhelp(EXIT_FAILURE);
+               }
+               ctl->changed = 1;
+               ctl->interactive = 0;
        }
-       /* all other options must have an argument. */
-       if (! optarg) {
-           usage (stderr);
-           exit (-1);
+       if (status != 0)
+               exit(EXIT_FAILURE);
+       /* done parsing arguments.  check for a username. */
+       if (optind < argc) {
+               if (optind + 1 < argc) {
+                       warnx(_("cannot handle multiple usernames"));
+                       errtryhelp(EXIT_FAILURE);
+               }
+               ctl->username = argv[optind];
        }
-       /* ok, we were given an argument */
-       info_given = true;
-       status = 0;
-
-       strncpy (buf, whoami, sizeof(buf)-128); 
-       buf[sizeof(buf)-128-1] = 0;
-       strcat (buf, ": ");
-
-       /* now store the argument */
-       switch (c) {
-       case 'f':
-           pinfo->full_name = optarg;
-           strcat (buf, "full name");
-           status = check_gecos_string (buf, optarg);
-           break;
-       case 'o':
-           pinfo->office = optarg;
-           strcat (buf, "office");
-           status = check_gecos_string (buf, optarg);
-           break;
-       case 'p':
-           pinfo->office_phone = optarg;
-           strcat (buf, "office phone");
-           status = check_gecos_string (buf, optarg);
-           break;
-       case 'h':
-           pinfo->home_phone = optarg;
-           strcat (buf, "home phone");
-           status = check_gecos_string (buf, optarg);
-           break;
-       default:
-           usage (stderr);
-           status = (-1);
-       }
-       if (status < 0) exit (status);
-    }
-    /* done parsing arguments. check for a username. */
-    if (optind < argc) {
-       if (optind + 1 < argc) {
-           usage (stderr);
-           exit (-1);
-       }
-       pinfo->username = argv[optind];
-    }
-    return (! info_given);
+       return;
 }
 
 /*
- *  usage () --
- *     print out a usage message.
+ *  parse_passwd () --
+ *     take a struct password and fill in the fields of the struct finfo.
  */
-static void usage (fp)
-    FILE *fp;
+static void parse_passwd(struct chfn_control *ctl)
 {
-    fprintf (fp, _("Usage: %s [ -f full-name ] [ -o office ] "), whoami);
-    fprintf (fp, _("[ -p office-phone ]\n      [ -h home-phone ] "));
-    fprintf (fp, _("[ --help ] [ --version ]\n"));
+       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, ",");
 }
 
 /*
- *  parse_passwd () --
- *     take a struct password and fill in the fields of the
- *     struct finfo.
+ *  ask_new_field () --
+ *     ask the user for a given field and check that the string is legal.
  */
-static void parse_passwd (pw, pinfo)
-    struct passwd *pw;
-    struct finfo *pinfo;
+static char *ask_new_field(struct chfn_control *ctl, const char *question,
+                          char *def_val)
 {
-    char *gecos;
-    char *cp;
+       int len;
+       char *buf;
+#ifndef HAVE_LIBREADLINE
+       size_t dummy = 0;
+#endif
 
-    if (pw) {
-       pinfo->pw = pw;
-       pinfo->username = pw->pw_name;
-       /* use pw_gecos - we take a copy since PAM destroys the original */
-       gecos = strdup(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 (!def_val)
+               def_val = "";
+       while (true) {
+               printf("%s [%s]: ", question, def_val);
+               __fpurge(stdin);
+#ifdef HAVE_LIBREADLINE
+               rl_bind_key('\t', rl_insert);
+               if ((buf = readline(NULL)) == NULL)
+#else
+               if (getline(&buf, &dummy, stdin) < 0)
+#endif
+                       errx(EXIT_FAILURE, _("Aborted."));
+               /* remove white spaces from string end */
+               ltrim_whitespace((unsigned char *) buf);
+               len = rtrim_whitespace((unsigned char *) buf);
+               if (len == 0) {
+                       free(buf);
+                       return xstrdup(def_val);
+               }
+               if (!strcasecmp(buf, "none")) {
+                       free(buf);
+                       ctl->changed = 1;
+                       return xstrdup("");
+               }
+               if (check_gecos_string(question, buf) >= 0)
+                       break;
+       }
+       ctl->changed = 1;
+       return buf;
 }
 
 /*
- *  ask_info () --
- *     prompt the user for the finger information and store it.
+ *  get_login_defs()
+ *     find /etc/login.defs CHFN_RESTRICT and save restrictions to run time
  */
-static void ask_info (oldfp, newfp)
-    struct finfo *oldfp;
-    struct finfo *newfp;
+static void get_login_defs(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);
-    printf ("\n");
+       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;
 }
 
 /*
- *  prompt () --
- *     ask the user for a given field and check that the string is legal.
+ *  ask_info () --
+ *     prompt the user for the finger information and store it.
  */
-static char *prompt (question, def_val)
-    char *question;
-    char *def_val;
+static void ask_info(struct chfn_control *ctl)
 {
-    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) {
-           printf (_("\nAborted.\n"));
-           exit (-1);
-       }
-       /* 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;
+       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');
 }
 
 /*
- *  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).
+ *  find_field () --
+ *     find field value in uninteractive mode; can be new, old, or blank
  */
-static int check_gecos_string (msg, gecos)
-    char *msg;
-    char *gecos;
+static char *find_field(char *nf, char *of)
 {
-    int i, c;
-
-    if (strlen(gecos) > MAX_FIELD_SIZE) {
-       if (msg != NULL)
-           printf("%s: ", msg);
-       printf(_("field is too long.\n"));
-       return -1;
-    }
-
-    for (i = 0; i < strlen (gecos); i++) {
-       c = gecos[i];
-       if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
-           if (msg) printf ("%s: ", msg);
-           printf (_("'%c' is not allowed.\n"), c);
-           return (-1);
-       }
-       if (iscntrl (c)) {
-           if (msg) printf ("%s: ", msg);
-           printf (_("Control characters are not allowed.\n"));
-           return (-1);
-       }
-    }
-    return (0);
+       if (nf)
+               return nf;
+       if (of)
+               return of;
+       return xstrdup("");
 }
 
 /*
- *  set_changed_data () --
- *     incorporate the new data into the old finger info.
+ *  add_missing () --
+ *     add not supplied field values when in uninteractive mode
  */
-static boolean set_changed_data (oldfp, newfp)
-    struct finfo *oldfp;
-    struct finfo *newfp;
+static void add_missing(struct chfn_control *ctl)
 {
-    boolean 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;
+       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");
 }
 
 /*
@@ -451,56 +362,131 @@ static boolean set_changed_data (oldfp, newfp)
  *     save the given finger info in /etc/passwd.
  *     return zero on success.
  */
-static int save_new_data (pinfo)
-     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 = "";
-
-    /* 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;
-    }
-
-    /* write the new struct passwd to the passwd file. */
-    pinfo->pw->pw_gecos = gecos;
-    if (setpwnam (pinfo->pw) < 0) {
-       perror ("setpwnam");
-       printf( _("Finger information *NOT* changed.  Try again later.\n" ));
-       return (-1);
-    }
-    printf (_("Finger information changed.\n"));
-    return 0;
+       char *gecos;
+       int len;
+
+       /* create the new gecos string */
+       len = xasprintf(&gecos, "%s,%s,%s,%s,%s",
+                       ctl->newf.full_name,
+                       ctl->newf.office,
+                       ctl->newf.office_phone,
+                       ctl->newf.home_phone,
+                       ctl->newf.other);
+
+       /* remove trailing empty fields (but not subfields of ctl->newf.other) */
+       if (!ctl->newf.other || !*ctl->newf.other) {
+               while (len > 0 && gecos[len - 1] == ',')
+                       len--;
+               gecos[len] = 0;
+       }
+
+#ifdef HAVE_LIBUSER
+       if (set_value_libuser("chfn", ctl->username, ctl->pw->pw_uid,
+                       LU_GECOS, gecos) < 0) {
+#else /* HAVE_LIBUSER */
+       /* write the new struct passwd to the passwd file. */
+       ctl->pw->pw_gecos = gecos;
+       if (setpwnam(ctl->pw, ".chfn") < 0) {
+               warn("setpwnam failed");
+#endif
+               printf(_
+                      ("Finger information *NOT* changed.  Try again later.\n"));
+               return -1;
+       }
+       free(gecos);
+       printf(_("Finger information changed.\n"));
+       return 0;
 }
 
-/*
- *  xmalloc () -- malloc that never fails.
- */
-static void *xmalloc (bytes)
-    int bytes;
+int main(int argc, char **argv)
 {
-    void *vp;
-
-    vp = malloc (bytes);
-    if (! vp && bytes > 0) {
-       perror (_("malloc failed"));
-       exit (-1);
-    }
-    return vp;
+       uid_t uid;
+       struct chfn_control ctl = {
+               .interactive = 1
+       };
+
+       sanitize_env();
+       setlocale(LC_ALL, "");  /* both for messages and for iscntrl() below */
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+       close_stdout_atexit();
+
+       uid = getuid();
+
+       /* check /etc/login.defs CHFN_RESTRICT */
+       get_login_defs(&ctl);
+
+       parse_argv(&ctl, argc, argv);
+       if (!ctl.username) {
+               ctl.pw = getpwuid(uid);
+               if (!ctl.pw)
+                       errx(EXIT_FAILURE, _("you (user %d) don't exist."),
+                            uid);
+               ctl.username = ctl.pw->pw_name;
+       } else {
+               ctl.pw = getpwnam(ctl.username);
+               if (!ctl.pw)
+                       errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
+                            ctl.username);
+       }
+       parse_passwd(&ctl);
+#ifndef HAVE_LIBUSER
+       if (!(is_local(ctl.username)))
+               errx(EXIT_FAILURE, _("can only change local entries"));
+#endif
+
+#ifdef HAVE_LIBSELINUX
+       if (is_selinux_enabled() > 0) {
+               if (uid == 0) {
+                       access_vector_t av = get_access_vector("passwd", "chfn");
+
+                       if (selinux_check_passwd_access(av) != 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;
 }